op/clip: compute bounds during Path build

The current renderer transforms and processes paths before sending them
to the GPU. It can compute bounds during processing.

The new renderer passes paths verbatim to the GPU, but needs the bounds
for constructing clip bounds.

This change computes the bounds during construction, so it is available
at use. As a bonus for storing the bounds with the path, path caches
(such as for storing text fragments) automatically reuse the bounds
calculations as well.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2021-03-26 13:06:42 +01:00
parent 157430a3d2
commit 06ce077436
3 changed files with 69 additions and 12 deletions
+1 -2
View File
@@ -760,8 +760,7 @@ func (r *renderer) packStencils(pops *[]*pathOp) {
*pops = ops
}
// intersects intersects clip and b where b is offset by off.
// ceilRect returns a bounding image.Rectangle for a f32.Rectangle.
// boundRectF returns a bounding image.Rectangle for a f32.Rectangle.
func boundRectF(r f32.Rectangle) image.Rectangle {
return image.Rectangle{
Min: image.Point{
+65 -9
View File
@@ -18,8 +18,7 @@ import (
// Op represents a clip area. Op intersects the current clip area with
// itself.
type Op struct {
bounds image.Rectangle
path PathSpec
path PathSpec
outline bool
stroke StrokeStyle
@@ -47,7 +46,14 @@ func (p Op) Add(o *op.Ops) {
path.spec.Add(o)
}
bounds := path.bounds
if str.Width > 0 {
// Expand bounds to cover stroke.
half := int(str.Width*.5 + .5)
bounds.Min.X -= half
bounds.Min.Y -= half
bounds.Max.X += half
bounds.Max.Y += half
data := o.Write(opconst.TypeStrokeLen)
data[0] = byte(opconst.TypeStroke)
bo := binary.LittleEndian
@@ -57,10 +63,10 @@ func (p Op) Add(o *op.Ops) {
data := o.Write(opconst.TypeClipLen)
data[0] = byte(opconst.TypeClip)
bo := binary.LittleEndian
bo.PutUint32(data[1:], uint32(p.bounds.Min.X))
bo.PutUint32(data[5:], uint32(p.bounds.Min.Y))
bo.PutUint32(data[9:], uint32(p.bounds.Max.X))
bo.PutUint32(data[13:], uint32(p.bounds.Max.Y))
bo.PutUint32(data[1:], uint32(bounds.Min.X))
bo.PutUint32(data[5:], uint32(bounds.Min.Y))
bo.PutUint32(data[9:], uint32(bounds.Max.X))
bo.PutUint32(data[13:], uint32(bounds.Max.Y))
if outline {
data[17] = byte(1)
}
@@ -133,6 +139,7 @@ type PathSpec struct {
open bool
// hasSegments tracks whether there are any segments in the path.
hasSegments bool
bounds image.Rectangle
}
// Path constructs a Op clip path described by lines and
@@ -150,6 +157,7 @@ type Path struct {
macro op.MacroOp
start f32.Point
hasSegments bool
bounds f32.Rectangle
}
// Pos returns the current pen position.
@@ -171,6 +179,7 @@ func (p *Path) End() PathSpec {
spec: c,
open: p.open || p.pen != p.start,
hasSegments: p.hasSegments,
bounds: boundRectF(p.bounds),
}
}
@@ -206,7 +215,51 @@ func (p *Path) LineTo(to f32.Point) {
bo.PutUint32(data[0:], uint32(p.contour))
ops.EncodeCommand(data[4:], scene.Line(p.pen, to))
p.pen = to
p.hasSegments = true
p.expand(to)
}
func (p *Path) expand(pt f32.Point) {
if !p.hasSegments {
p.hasSegments = true
p.bounds = f32.Rectangle{Min: pt, Max: pt}
} else {
b := p.bounds
if pt.X < b.Min.X {
b.Min.X = pt.X
}
if pt.Y < b.Min.Y {
b.Min.Y = pt.Y
}
if pt.X > b.Max.X {
b.Max.X = pt.X
}
if pt.Y > b.Max.Y {
b.Max.Y = pt.Y
}
p.bounds = b
}
}
// boundRectF returns a bounding image.Rectangle for a f32.Rectangle.
func boundRectF(r f32.Rectangle) image.Rectangle {
return image.Rectangle{
Min: image.Point{
X: int(floor(r.Min.X)),
Y: int(floor(r.Min.Y)),
},
Max: image.Point{
X: int(ceil(r.Max.X)),
Y: int(ceil(r.Max.Y)),
},
}
}
func ceil(v float32) int {
return int(math.Ceil(float64(v)))
}
func floor(v float32) int {
return int(math.Floor(float64(v)))
}
// Quad records a quadratic Bézier from the pen to end
@@ -225,7 +278,8 @@ func (p *Path) QuadTo(ctrl, to f32.Point) {
bo.PutUint32(data[0:], uint32(p.contour))
ops.EncodeCommand(data[4:], scene.Quad(p.pen, ctrl, to))
p.pen = to
p.hasSegments = true
p.expand(ctrl)
p.expand(to)
}
// Arc adds an elliptical arc to the path. The implied ellipse is defined
@@ -265,7 +319,9 @@ func (p *Path) CubeTo(ctrl0, ctrl1, to f32.Point) {
bo.PutUint32(data[0:], uint32(p.contour))
ops.EncodeCommand(data[4:], scene.Cubic(p.pen, ctrl0, ctrl1, to))
p.pen = to
p.hasSegments = true
p.expand(ctrl0)
p.expand(ctrl1)
p.expand(to)
}
// Close closes the path contour.
+3 -1
View File
@@ -16,8 +16,10 @@ type Rect image.Rectangle
// Op returns the op for the rectangle.
func (r Rect) Op() Op {
return Op{
bounds: image.Rectangle(r),
outline: true,
path: PathSpec{
bounds: image.Rectangle(r),
},
}
}