From fa2c741fcfbc39b8bd7eb1d263db01f26236ed2f Mon Sep 17 00:00:00 2001 From: faiface Date: Sat, 27 May 2017 13:14:13 +0200 Subject: [PATCH 1/6] 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 1fd110ce4c8e8ffea1743bd49a7c54ea62d0b14f Mon Sep 17 00:00:00 2001 From: faiface Date: Sat, 10 Jun 2017 01:10:59 +0200 Subject: [PATCH 2/6] 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 f68301dcd84ce2743b16bda016059c74217bbe15 Mon Sep 17 00:00:00 2001 From: Seebs Date: Fri, 9 Jun 2017 19:19:32 -0500 Subject: [PATCH 3/6] 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 678da34fc34f76f9edc4e0967189a74cf9d4318d Mon Sep 17 00:00:00 2001 From: Seebs Date: Fri, 9 Jun 2017 20:19:17 -0500 Subject: [PATCH 4/6] 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 daedc45ea933ab83310b2a001917e7f3202fc11b Mon Sep 17 00:00:00 2001 From: Seebs Date: Fri, 9 Jun 2017 21:42:20 -0500 Subject: [PATCH 5/6] 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 ee5d49dbd39f7313f805cd14221d53d80fbd31ea Mon Sep 17 00:00:00 2001 From: Seebs Date: Fri, 9 Jun 2017 22:22:27 -0500 Subject: [PATCH 6/6] 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,