From 67a69d96d6ace97f267db0adb5636796d723dbfd Mon Sep 17 00:00:00 2001 From: faiface Date: Sat, 27 May 2017 13:14:13 +0200 Subject: [PATCH 1/7] switch to OpenGL 2.1 --- pixelgl/canvas.go | 4 ++-- pixelgl/window.go | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/pixelgl/canvas.go b/pixelgl/canvas.go index 070a382..7a6400e 100644 --- a/pixelgl/canvas.go +++ b/pixelgl/canvas.go @@ -370,7 +370,7 @@ var canvasUniformFormat = glhf.AttrFormat{ } var canvasVertexShader = ` -#version 330 core +#version 130 in vec2 position; in vec4 color; @@ -395,7 +395,7 @@ void main() { ` var canvasFragmentShader = ` -#version 330 core +#version 130 in vec4 Color; in vec2 TexCoords; diff --git a/pixelgl/window.go b/pixelgl/window.go index afbd5f7..76523a5 100644 --- a/pixelgl/window.go +++ b/pixelgl/window.go @@ -90,10 +90,8 @@ func NewWindow(cfg WindowConfig) (*Window, error) { err := mainthread.CallErr(func() error { var err error - glfw.WindowHint(glfw.ContextVersionMajor, 3) - glfw.WindowHint(glfw.ContextVersionMinor, 3) - glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile) - glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True) + glfw.WindowHint(glfw.ContextVersionMajor, 2) + glfw.WindowHint(glfw.ContextVersionMinor, 1) glfw.WindowHint(glfw.Resizable, bool2int[cfg.Resizable]) glfw.WindowHint(glfw.Decorated, bool2int[!cfg.Undecorated]) From f80edafc7bb149cc8862bbe95783d96dd907f747 Mon Sep 17 00:00:00 2001 From: faiface Date: Sat, 27 May 2017 19:14:25 +0200 Subject: [PATCH 2/7] remove profiling from typewriter example --- examples/typewriter/main.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/typewriter/main.go b/examples/typewriter/main.go index 820fb2c..d7691ff 100644 --- a/examples/typewriter/main.go +++ b/examples/typewriter/main.go @@ -13,7 +13,6 @@ import ( "github.com/faiface/pixel/pixelgl" "github.com/faiface/pixel/text" "github.com/golang/freetype/truetype" - "github.com/pkg/profile" "golang.org/x/image/colornames" "golang.org/x/image/font" "golang.org/x/image/font/gofont/gobold" @@ -317,6 +316,5 @@ func run() { } func main() { - defer profile.Start(profile.MemProfile).Stop() pixelgl.Run(run) } From 31fc049ab76e3ac2ca748ca04557af721f0e2cf2 Mon Sep 17 00:00:00 2001 From: faiface Date: Sat, 10 Jun 2017 01:10:59 +0200 Subject: [PATCH 3/7] optimize GLTriangles SetLen and Update --- pixelgl/gltriangles.go | 61 +++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/pixelgl/gltriangles.go b/pixelgl/gltriangles.go index bf7a895..6493270 100644 --- a/pixelgl/gltriangles.go +++ b/pixelgl/gltriangles.go @@ -60,9 +60,10 @@ func (gt *GLTriangles) Len() int { // SetLen efficiently resizes GLTriangles to len. // // Time complexity is amortized O(1). -func (gt *GLTriangles) SetLen(len int) { - if len > gt.Len() { - needAppend := len - gt.Len() +func (gt *GLTriangles) SetLen(length int) { + switch { + case length > gt.Len(): + needAppend := length - gt.Len() for i := 0; i < needAppend; i++ { gt.data = append(gt.data, 0, 0, @@ -71,11 +72,16 @@ func (gt *GLTriangles) SetLen(len int) { 0, ) } + case length < gt.Len(): + gt.data = gt.data[:length*gt.vs.Stride()] + default: + return } - if len < gt.Len() { - gt.data = gt.data[:len*gt.vs.Stride()] - } - gt.submitData() + mainthread.CallNonBlock(func() { + gt.vs.Begin() + gt.vs.SetLen(length) + gt.vs.End() + }) } // Slice returns a sub-Triangles of this GLTriangles in range [i, j). @@ -144,29 +150,6 @@ func (gt *GLTriangles) updateData(t pixel.Triangles) { } } -func (gt *GLTriangles) submitData() { - // this code is supposed to copy the vertex data and CallNonBlock the update if - // the data is small enough, otherwise it'll block and not copy the data - if len(gt.data) < 256 { // arbitrary heurestic constant - data := append([]float32{}, gt.data...) - mainthread.CallNonBlock(func() { - gt.vs.Begin() - dataLen := len(data) / gt.vs.Stride() - gt.vs.SetLen(dataLen) - gt.vs.SetVertexData(data) - gt.vs.End() - }) - } else { - mainthread.Call(func() { - gt.vs.Begin() - dataLen := len(gt.data) / gt.vs.Stride() - gt.vs.SetLen(dataLen) - gt.vs.SetVertexData(gt.data) - gt.vs.End() - }) - } -} - // Update copies vertex properties from the supplied Triangles into this GLTriangles. // // The two Triangles (gt and t) must be of the same len. @@ -175,7 +158,23 @@ func (gt *GLTriangles) Update(t pixel.Triangles) { panic(fmt.Errorf("(%T).Update: invalid triangles len", gt)) } gt.updateData(t) - gt.submitData() + + // this code is supposed to copy the vertex data and CallNonBlock the update if + // the data is small enough, otherwise it'll block and not copy the data + if len(gt.data) < 256 { // arbitrary heurestic constant + data := append([]float32{}, gt.data...) + mainthread.CallNonBlock(func() { + gt.vs.Begin() + gt.vs.SetVertexData(data) + gt.vs.End() + }) + } else { + mainthread.Call(func() { + gt.vs.Begin() + gt.vs.SetVertexData(gt.data) + gt.vs.End() + }) + } } // Copy returns an independent copy of this GLTriangles. From 9a6e6066bd2fec72ed61bfab2ce35e743a38133a Mon Sep 17 00:00:00 2001 From: Seebs Date: Fri, 9 Jun 2017 19:19:32 -0500 Subject: [PATCH 4/7] don't call Len() when it can't change updateData()'s loops checking gt.Len() turns out to have been costing significant computation, not least because each call then in turn called gt.vs.Stride(). --- pixelgl/gltriangles.go | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/pixelgl/gltriangles.go b/pixelgl/gltriangles.go index 6493270..52ed0c9 100644 --- a/pixelgl/gltriangles.go +++ b/pixelgl/gltriangles.go @@ -101,16 +101,17 @@ func (gt *GLTriangles) updateData(t pixel.Triangles) { } // TrianglesData short path + stride := gt.vs.Stride() + length := gt.Len() if t, ok := t.(*pixel.TrianglesData); ok { - for i := 0; i < gt.Len(); i++ { + for i := 0; i < length; i++ { var ( px, py = (*t)[i].Position.XY() col = (*t)[i].Color tx, ty = (*t)[i].Picture.XY() in = (*t)[i].Intensity ) - s := gt.vs.Stride() - d := gt.data[i*s : i*s+9] + d := gt.data[i*stride : i*stride+9] d[0] = float32(px) d[1] = float32(py) d[2] = float32(col.R) @@ -125,27 +126,27 @@ func (gt *GLTriangles) updateData(t pixel.Triangles) { } if t, ok := t.(pixel.TrianglesPosition); ok { - for i := 0; i < gt.Len(); i++ { + for i := 0; i < length; i++ { px, py := t.Position(i).XY() - gt.data[i*gt.vs.Stride()+0] = float32(px) - gt.data[i*gt.vs.Stride()+1] = float32(py) + gt.data[i*stride+0] = float32(px) + gt.data[i*stride+1] = float32(py) } } if t, ok := t.(pixel.TrianglesColor); ok { - for i := 0; i < gt.Len(); i++ { + for i := 0; i < length; i++ { col := t.Color(i) - gt.data[i*gt.vs.Stride()+2] = float32(col.R) - gt.data[i*gt.vs.Stride()+3] = float32(col.G) - gt.data[i*gt.vs.Stride()+4] = float32(col.B) - gt.data[i*gt.vs.Stride()+5] = float32(col.A) + gt.data[i*stride+2] = float32(col.R) + gt.data[i*stride+3] = float32(col.G) + gt.data[i*stride+4] = float32(col.B) + gt.data[i*stride+5] = float32(col.A) } } if t, ok := t.(pixel.TrianglesPicture); ok { - for i := 0; i < gt.Len(); i++ { + for i := 0; i < length; i++ { pic, intensity := t.Picture(i) - gt.data[i*gt.vs.Stride()+6] = float32(pic.X) - gt.data[i*gt.vs.Stride()+7] = float32(pic.Y) - gt.data[i*gt.vs.Stride()+8] = float32(intensity) + gt.data[i*stride+6] = float32(pic.X) + gt.data[i*stride+7] = float32(pic.Y) + gt.data[i*stride+8] = float32(intensity) } } } From ef6a44fef85629e8c1cc6db1e91b44f95f6c961a Mon Sep 17 00:00:00 2001 From: Seebs Date: Fri, 9 Jun 2017 20:19:17 -0500 Subject: [PATCH 5/7] Slightly clean up normal calculations We never actually need the "normal" value; it's an extra calculation we didn't need, because ijNormal is the same value early on. It's totally possible that we could further simplify this; there's a lot of time going into the normal computations. --- imdraw/imdraw.go | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/imdraw/imdraw.go b/imdraw/imdraw.go index f7ce769..ab72ea9 100644 --- a/imdraw/imdraw.go +++ b/imdraw/imdraw.go @@ -528,29 +528,28 @@ func (imd *IMDraw) polyline(thickness float64, closed bool) { // first point j, i := 0, 1 - normal := points[i].pos.Sub(points[j].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2) + ijNormal := points[1].pos.Sub(points[0].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2) if !closed { switch points[j].endshape { case NoEndShape: // nothing case SharpEndShape: - imd.pushPt(points[j].pos.Add(normal), points[j]) - imd.pushPt(points[j].pos.Sub(normal), points[j]) - imd.pushPt(points[j].pos.Add(normal.Rotated(math.Pi/2)), points[j]) + imd.pushPt(points[j].pos.Add(ijNormal), points[j]) + imd.pushPt(points[j].pos.Sub(ijNormal), points[j]) + imd.pushPt(points[j].pos.Add(ijNormal.Rotated(math.Pi/2)), points[j]) imd.fillPolygon() case RoundEndShape: imd.pushPt(points[j].pos, points[j]) - imd.fillEllipseArc(pixel.V(thickness/2, thickness/2), normal.Angle(), normal.Angle()+math.Pi) + imd.fillEllipseArc(pixel.V(thickness/2, thickness/2), ijNormal.Angle(), ijNormal.Angle()+math.Pi) } } - imd.pushPt(points[j].pos.Add(normal), points[j]) - imd.pushPt(points[j].pos.Sub(normal), points[j]) + imd.pushPt(points[j].pos.Add(ijNormal), points[j]) + imd.pushPt(points[j].pos.Sub(ijNormal), points[j]) // middle points // compute "previous" normal: - ijNormal := points[1].pos.Sub(points[0].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2) for i := 0; i < len(points); i++ { j, k := i+1, i+2 @@ -602,10 +601,10 @@ func (imd *IMDraw) polyline(thickness float64, closed bool) { // last point i, j = len(points)-2, len(points)-1 - normal = points[j].pos.Sub(points[i].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2) + ijNormal = points[j].pos.Sub(points[i].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2) - imd.pushPt(points[j].pos.Sub(normal), points[j]) - imd.pushPt(points[j].pos.Add(normal), points[j]) + imd.pushPt(points[j].pos.Sub(ijNormal), points[j]) + imd.pushPt(points[j].pos.Add(ijNormal), points[j]) imd.fillPolygon() if !closed { @@ -613,13 +612,13 @@ func (imd *IMDraw) polyline(thickness float64, closed bool) { case NoEndShape: // nothing case SharpEndShape: - imd.pushPt(points[j].pos.Add(normal), points[j]) - imd.pushPt(points[j].pos.Sub(normal), points[j]) - imd.pushPt(points[j].pos.Add(normal.Rotated(-math.Pi/2)), points[j]) + imd.pushPt(points[j].pos.Add(ijNormal), points[j]) + imd.pushPt(points[j].pos.Sub(ijNormal), points[j]) + imd.pushPt(points[j].pos.Add(ijNormal.Rotated(-math.Pi/2)), points[j]) imd.fillPolygon() case RoundEndShape: imd.pushPt(points[j].pos, points[j]) - imd.fillEllipseArc(pixel.V(thickness/2, thickness/2), normal.Angle(), normal.Angle()-math.Pi) + imd.fillEllipseArc(pixel.V(thickness/2, thickness/2), ijNormal.Angle(), ijNormal.Angle()-math.Pi) } } From f2ef87f1983a3797229d6625cb92f2da7b94d2a4 Mon Sep 17 00:00:00 2001 From: Seebs Date: Fri, 9 Jun 2017 21:42:20 -0500 Subject: [PATCH 6/7] Improve normal calculations Soooo. It turns out that the bunch of smallish (~4-5% of runtime) loads associated with Len(), Unit(), Rotated(), and so on... Were actually more like 15% or more of computational effort. I first figured this out by creating: func (u Vec) Normal(v Vec) Vec which gives you a vector normal to u->v. That consumed a lot of CPU time, and was followed by .Unit().Scaled(imd.thickness / 2), which consumed a bit more CPU time. After some poking, and in the interests of avoiding UI cruft, the final selection is func (u Vec) Normal() Vec This returns the vector rotated 90 degrees, which turns out to be the most common problem. --- geometry.go | 13 +++++++++++++ imdraw/imdraw.go | 15 +++++++-------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/geometry.go b/geometry.go index 13a574d..02a8b51 100644 --- a/geometry.go +++ b/geometry.go @@ -49,6 +49,11 @@ func (u Vec) XY() (x, y float64) { return u.X, u.Y } +// Normal returns a vector normal to u (rotated by math.pi/2) +func (u Vec) Normal() Vec { + return Vec{X: u.Y, Y: -u.X} +} + // Add returns the sum of vectors u and v. func (u Vec) Add(v Vec) Vec { return Vec{ @@ -65,6 +70,14 @@ func (u Vec) Sub(v Vec) Vec { } } +// To returns the vector from vector u to vector v, equivalent to v.Sub(u). +func (u Vec) To(v Vec) Vec { + return Vec{ + v.X - u.X, + v.Y - u.Y, + } +} + // Scaled returns the vector u multiplied by c. func (u Vec) Scaled(c float64) Vec { return Vec{u.X * c, u.Y * c} diff --git a/imdraw/imdraw.go b/imdraw/imdraw.go index ab72ea9..7f72a4d 100644 --- a/imdraw/imdraw.go +++ b/imdraw/imdraw.go @@ -495,12 +495,12 @@ func (imd *IMDraw) outlineEllipseArc(radius pixel.Vec, low, high, thickness floa thick := pixel.V(thickness/2, 0).Rotated(normalLow) imd.pushPt(lowCenter.Add(thick), pt) imd.pushPt(lowCenter.Sub(thick), pt) - imd.pushPt(lowCenter.Sub(thick.Rotated(math.Pi/2*orientation)), pt) + imd.pushPt(lowCenter.Sub(thick.Normal().Scaled(orientation)), pt) imd.fillPolygon() thick = pixel.V(thickness/2, 0).Rotated(normalHigh) imd.pushPt(highCenter.Add(thick), pt) imd.pushPt(highCenter.Sub(thick), pt) - imd.pushPt(highCenter.Add(thick.Rotated(math.Pi/2*orientation)), pt) + imd.pushPt(highCenter.Add(thick.Normal().Scaled(orientation)), pt) imd.fillPolygon() case RoundEndShape: imd.pushPt(lowCenter, pt) @@ -528,7 +528,7 @@ func (imd *IMDraw) polyline(thickness float64, closed bool) { // first point j, i := 0, 1 - ijNormal := points[1].pos.Sub(points[0].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2) + ijNormal := points[0].pos.To(points[1].pos).Normal().Unit().Scaled(thickness / 2) if !closed { switch points[j].endshape { @@ -537,7 +537,7 @@ func (imd *IMDraw) polyline(thickness float64, closed bool) { case SharpEndShape: imd.pushPt(points[j].pos.Add(ijNormal), points[j]) imd.pushPt(points[j].pos.Sub(ijNormal), points[j]) - imd.pushPt(points[j].pos.Add(ijNormal.Rotated(math.Pi/2)), points[j]) + imd.pushPt(points[j].pos.Add(ijNormal.Normal()), points[j]) imd.fillPolygon() case RoundEndShape: imd.pushPt(points[j].pos, points[j]) @@ -549,7 +549,6 @@ func (imd *IMDraw) polyline(thickness float64, closed bool) { imd.pushPt(points[j].pos.Sub(ijNormal), points[j]) // middle points - // compute "previous" normal: for i := 0; i < len(points); i++ { j, k := i+1, i+2 @@ -565,7 +564,7 @@ func (imd *IMDraw) polyline(thickness float64, closed bool) { k %= len(points) } - jkNormal := points[k].pos.Sub(points[j].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2) + jkNormal := points[j].pos.To(points[k].pos).Normal().Unit().Scaled(thickness / 2) orientation := 1.0 if ijNormal.Cross(jkNormal) > 0 { @@ -601,7 +600,7 @@ func (imd *IMDraw) polyline(thickness float64, closed bool) { // last point i, j = len(points)-2, len(points)-1 - ijNormal = points[j].pos.Sub(points[i].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2) + ijNormal = points[i].pos.To(points[j].pos).Normal().Unit().Scaled(thickness / 2) imd.pushPt(points[j].pos.Sub(ijNormal), points[j]) imd.pushPt(points[j].pos.Add(ijNormal), points[j]) @@ -614,7 +613,7 @@ func (imd *IMDraw) polyline(thickness float64, closed bool) { case SharpEndShape: imd.pushPt(points[j].pos.Add(ijNormal), points[j]) imd.pushPt(points[j].pos.Sub(ijNormal), points[j]) - imd.pushPt(points[j].pos.Add(ijNormal.Rotated(-math.Pi/2)), points[j]) + imd.pushPt(points[j].pos.Add(ijNormal.Normal().Scaled(-1)), points[j]) imd.fillPolygon() case RoundEndShape: imd.pushPt(points[j].pos, points[j]) From 0dc27e409b103b3608c08382aa13894ceee17c25 Mon Sep 17 00:00:00 2001 From: Seebs Date: Fri, 9 Jun 2017 22:22:27 -0500 Subject: [PATCH 7/7] Push: Don't convert pixel.RGBA to pixel.RGBA Because that's expensive, even in the case where the conversion is trivial. Use type assertion first. Reduces runtime cost of imdraw.Push from ~15.3% to 8.4%, so not-quite-50% of runtime cost of pushing points. If you were setting imd.Color to Color objects that aren't RGBA every single point, not much help. But if you set it and then draw a bunch of points, this will be a big win. --- imdraw/imdraw.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/imdraw/imdraw.go b/imdraw/imdraw.go index 7f72a4d..e616c39 100644 --- a/imdraw/imdraw.go +++ b/imdraw/imdraw.go @@ -128,7 +128,9 @@ func (imd *IMDraw) Draw(t pixel.Target) { // Push adds some points to the IM queue. All Pushed points will have the same properties except for // the position. func (imd *IMDraw) Push(pts ...pixel.Vec) { - imd.Color = pixel.ToRGBA(imd.Color) + if _, ok := imd.Color.(pixel.RGBA); !ok { + imd.Color = pixel.ToRGBA(imd.Color) + } opts := point{ col: imd.Color.(pixel.RGBA), pic: imd.Picture,