Skip to content

Commit

Permalink
Merge pull request #2217 from pwiecz/develop
Browse files Browse the repository at this point in the history
Use OpenGL to draw canvas lines
  • Loading branch information
andydotxyz committed May 8, 2021
2 parents 9ec9882 + 5719303 commit 6af63b0
Show file tree
Hide file tree
Showing 6 changed files with 375 additions and 15 deletions.
47 changes: 45 additions & 2 deletions internal/painter/gl/draw.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@ func (p *glPainter) drawCircle(circle *canvas.Circle, pos fyne.Position, frame f
}

func (p *glPainter) drawLine(line *canvas.Line, pos fyne.Position, frame fyne.Size) {
p.drawTextureWithDetails(line, p.newGlLineTexture, pos, line.Size(), frame, canvas.ImageFillStretch,
1.0, painter.VectorPad(line))
points, halfWidth, feather := p.lineCoords(pos, line.Position1, line.Position2, line.StrokeWidth, 0.5, frame)
vbo := p.glCreateLineBuffer(points)
p.glDrawLine(halfWidth, line.StrokeColor, feather)
p.glFreeBuffer(vbo)
}

func (p *glPainter) drawImage(img *canvas.Image, pos fyne.Position, frame fyne.Size) {
Expand Down Expand Up @@ -102,6 +104,47 @@ func (p *glPainter) drawObject(o fyne.CanvasObject, pos fyne.Position, frame fyn
}
}

func (p *glPainter) lineCoords(pos, pos1, pos2 fyne.Position, lineWidth, feather float32, frame fyne.Size) ([]float32, float32, float32) {
// Shift line coordinates so that they match the target position.
xPosDiff := pos.X - fyne.Min(pos1.X, pos2.X)
yPosDiff := pos.Y - fyne.Min(pos1.Y, pos2.Y)
pos1.X = roundToPixel(pos1.X+xPosDiff, p.pixScale)
pos1.Y = roundToPixel(pos1.Y+yPosDiff, p.pixScale)
pos2.X = roundToPixel(pos2.X+xPosDiff, p.pixScale)
pos2.Y = roundToPixel(pos2.Y+yPosDiff, p.pixScale)

x1Pos := pos1.X / frame.Width
x1 := -1 + x1Pos*2
y1Pos := pos1.Y / frame.Height
y1 := 1 - y1Pos*2
x2Pos := pos2.X / frame.Width
x2 := -1 + x2Pos*2
y2Pos := pos2.Y / frame.Height
y2 := 1 - y2Pos*2

normalX := (pos2.Y - pos1.Y) / frame.Width
normalY := (pos2.X - pos1.X) / frame.Height
dirLength := float32(math.Sqrt(float64(normalX*normalX + normalY*normalY)))
normalX /= dirLength
normalY /= dirLength

normalObjX := normalX * 0.5 * frame.Width
normalObjY := normalY * 0.5 * frame.Height
widthMultiplier := float32(math.Sqrt(float64(normalObjX*normalObjX + normalObjY*normalObjY)))
halfWidth := (lineWidth*0.5 + feather) / widthMultiplier
featherWidth := feather / widthMultiplier

return []float32{
// coord x, y normal x, y
x1, y1, normalX, normalY,
x2, y2, normalX, normalY,
x2, y2, -normalX, -normalY,
x2, y2, -normalX, -normalY,
x1, y1, normalX, normalY,
x1, y1, -normalX, -normalY,
}, halfWidth, featherWidth
}

// rectCoords calculates the openGL coordinate space of a rectangle
func (p *glPainter) rectCoords(size fyne.Size, pos fyne.Position, frame fyne.Size,
fill canvas.ImageFill, aspect float32, pad float32) []float32 {
Expand Down
7 changes: 0 additions & 7 deletions internal/painter/gl/gl_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,6 @@ func (p *glPainter) newGlCircleTexture(obj fyne.CanvasObject) Texture {
return p.imgToTexture(raw, canvas.ImageScaleSmooth)
}

func (p *glPainter) newGlLineTexture(obj fyne.CanvasObject) Texture {
line := obj.(*canvas.Line)
raw := painter.DrawLine(line, painter.VectorPad(line), p.textureScale)

return p.imgToTexture(raw, canvas.ImageScaleSmooth)
}

func (p *glPainter) newGlRectTexture(obj fyne.CanvasObject) Texture {
rect := obj.(*canvas.Rectangle)
if rect.StrokeColor != nil && rect.StrokeWidth > 0 {
Expand Down
108 changes: 108 additions & 0 deletions internal/painter/gl/gl_core.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package gl
import (
"fmt"
"image"
"image/color"
"image/draw"
"strings"

Expand Down Expand Up @@ -157,6 +158,42 @@ const (
gl_FragColor = texture2D(tex, fragTexCoord);
}
` + "\x00"

vertexLineShaderSource = `
#version 110
attribute vec2 vert;
attribute vec2 normal;
uniform float lineWidth;
varying vec2 delta;
void main() {
delta = normal * lineWidth;
gl_Position = vec4(vert + delta, 0, 1);
}
` + "\x00"

fragmentLineShaderSource = `
#version 110
uniform vec4 color;
uniform float lineWidth;
uniform float feather;
varying vec2 delta;
void main() {
float alpha = color.a;
float distance = length(delta);
if (feather == 0.0 || distance <= lineWidth - feather) {
gl_FragColor = color;
} else {
gl_FragColor = vec4(color.r, color.g, color.b, mix(color.a, 0.0, (distance - (lineWidth - feather)) / feather));
}
}
` + "\x00"
)

func (p *glPainter) Init() {
Expand All @@ -176,6 +213,23 @@ func (p *glPainter) Init() {
logError()

p.program = Program(prog)

vertexLineShader, err := compileShader(vertexLineShaderSource, gl.VERTEX_SHADER)
if err != nil {
panic(err)
}
fragmentLineShader, err := compileShader(fragmentLineShaderSource, gl.FRAGMENT_SHADER)
if err != nil {
panic(err)
}

lineProg := gl.CreateProgram()
gl.AttachShader(lineProg, vertexLineShader)
gl.AttachShader(lineProg, fragmentLineShader)
gl.LinkProgram(lineProg)
logError()

p.lineProgram = Program(lineProg)
}

func (p *glPainter) glClearBuffer() {
Expand All @@ -201,6 +255,8 @@ func (p *glPainter) glScissorClose() {
}

func (p *glPainter) glCreateBuffer(points []float32) Buffer {
gl.UseProgram(uint32(p.program))

var vbo uint32
gl.GenBuffers(1, &vbo)
logError()
Expand All @@ -222,6 +278,30 @@ func (p *glPainter) glCreateBuffer(points []float32) Buffer {
return Buffer(vbo)
}

func (p *glPainter) glCreateLineBuffer(points []float32) Buffer {
gl.UseProgram(uint32(p.lineProgram))

var vbo uint32
gl.GenBuffers(1, &vbo)
logError()
gl.BindBuffer(gl.ARRAY_BUFFER, vbo)
logError()
gl.BufferData(gl.ARRAY_BUFFER, 4*len(points), gl.Ptr(points), gl.STATIC_DRAW)
logError()

vertAttrib := uint32(gl.GetAttribLocation(uint32(p.lineProgram), gl.Str("vert\x00")))
gl.EnableVertexAttribArray(vertAttrib)
gl.VertexAttribPointer(vertAttrib, 2, gl.FLOAT, false, 4*4, gl.PtrOffset(0))
logError()

normalAttrib := uint32(gl.GetAttribLocation(uint32(p.lineProgram), gl.Str("normal\x00")))
gl.EnableVertexAttribArray(normalAttrib)
gl.VertexAttribPointer(normalAttrib, 2, gl.FLOAT, false, 4*4, gl.PtrOffset(2*4))
logError()

return Buffer(vbo)
}

func (p *glPainter) glFreeBuffer(vbo Buffer) {
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
logError()
Expand All @@ -231,6 +311,8 @@ func (p *glPainter) glFreeBuffer(vbo Buffer) {
}

func (p *glPainter) glDrawTexture(texture Texture, alpha float32) {
gl.UseProgram(uint32(p.program))

// here we have to choose between blending the image alpha or fading it...
// TODO find a way to support both
if alpha != 1.0 {
Expand All @@ -249,6 +331,32 @@ func (p *glPainter) glDrawTexture(texture Texture, alpha float32) {
logError()
}

func (p *glPainter) glDrawLine(width float32, col color.Color, feather float32) {
gl.UseProgram(uint32(p.lineProgram))

gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
logError()

colorUniform := gl.GetUniformLocation(uint32(p.lineProgram), gl.Str("color\x00"))
r, g, b, a := col.RGBA()
if a == 0 {
gl.Uniform4f(colorUniform, 0, 0, 0, 0)
} else {
alpha := float32(a)
col := []float32{float32(r) / alpha, float32(g) / alpha, float32(b) / alpha, alpha / 0xffff}
gl.Uniform4fv(colorUniform, 1, &col[0])
}
lineWidthUniform := gl.GetUniformLocation(uint32(p.lineProgram), gl.Str("lineWidth\x00"))
gl.Uniform1f(lineWidthUniform, width)

featherUniform := gl.GetUniformLocation(uint32(p.lineProgram), gl.Str("feather\x00"))
gl.Uniform1f(featherUniform, feather)
logError()

gl.DrawArrays(gl.TRIANGLES, 0, 6)
logError()
}

func (p *glPainter) glCapture(width, height int32, pixels *[]uint8) {
gl.ReadBuffer(gl.FRONT)
logError()
Expand Down
107 changes: 107 additions & 0 deletions internal/painter/gl/gl_es.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package gl
import (
"fmt"
"image"
"image/color"
"image/draw"
"strings"

Expand Down Expand Up @@ -159,6 +160,42 @@ const (
gl_FragColor = texture2D(tex, fragTexCoord);
}
` + "\x00"

vertexLineShaderSource = `
#version 100
attribute vec2 vert;
attribute vec2 normal;
uniform float lineWidth;
varying highp vec2 delta;
void main() {
delta = normal * lineWidth;
gl_Position = vec4(vert + delta, 0, 1);
}
` + "\x00"

fragmentLineShaderSource = `
#version 100
uniform highp vec4 color;
uniform highp float lineWidth;
uniform highp float feather;
varying highp vec2 delta;
void main() {
highp float alpha = color.a;
highp float distance = length(delta);
if (feather == 0.0 || distance <= lineWidth - feather) {
gl_FragColor = color;
} else {
gl_FragColor = vec4(color.r, color.g, color.b, mix(color.a, 0.0, (distance - (lineWidth - feather)) / feather));
}
}
` + "\x00"
)

func (p *glPainter) Init() {
Expand All @@ -178,6 +215,23 @@ func (p *glPainter) Init() {
logError()

p.program = Program(prog)

vertexLineShader, err := compileShader(vertexLineShaderSource, gl.VERTEX_SHADER)
if err != nil {
panic(err)
}
fragmentLineShader, err := compileShader(fragmentLineShaderSource, gl.FRAGMENT_SHADER)
if err != nil {
panic(err)
}

lineProg := gl.CreateProgram()
gl.AttachShader(lineProg, vertexLineShader)
gl.AttachShader(lineProg, fragmentLineShader)
gl.LinkProgram(lineProg)
logError()

p.lineProgram = Program(lineProg)
}

func (p *glPainter) glClearBuffer() {
Expand All @@ -203,6 +257,8 @@ func (p *glPainter) glScissorClose() {
}

func (p *glPainter) glCreateBuffer(points []float32) Buffer {
gl.UseProgram(uint32(p.program))

var vbo uint32
gl.GenBuffers(1, &vbo)
logError()
Expand All @@ -224,6 +280,30 @@ func (p *glPainter) glCreateBuffer(points []float32) Buffer {
return Buffer(vbo)
}

func (p *glPainter) glCreateLineBuffer(points []float32) Buffer {
gl.UseProgram(uint32(p.lineProgram))

var vbo uint32
gl.GenBuffers(1, &vbo)
logError()
gl.BindBuffer(gl.ARRAY_BUFFER, vbo)
logError()
gl.BufferData(gl.ARRAY_BUFFER, 4*len(points), gl.Ptr(points), gl.STATIC_DRAW)
logError()

vertAttrib := uint32(gl.GetAttribLocation(uint32(p.lineProgram), gl.Str("vert\x00")))
gl.EnableVertexAttribArray(vertAttrib)
gl.VertexAttribPointer(vertAttrib, 2, gl.FLOAT, false, 4*4, gl.PtrOffset(0))
logError()

normalAttrib := uint32(gl.GetAttribLocation(uint32(p.lineProgram), gl.Str("normal\x00")))
gl.EnableVertexAttribArray(normalAttrib)
gl.VertexAttribPointer(normalAttrib, 2, gl.FLOAT, false, 4*4, gl.PtrOffset(2*4))
logError()

return Buffer(vbo)
}

func (p *glPainter) glFreeBuffer(vbo Buffer) {
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
logError()
Expand All @@ -233,6 +313,8 @@ func (p *glPainter) glFreeBuffer(vbo Buffer) {
}

func (p *glPainter) glDrawTexture(texture Texture, alpha float32) {
gl.UseProgram(uint32(p.program))

// here we have to choose between blending the image alpha or fading it...
// TODO find a way to support both
if alpha != 1.0 {
Expand All @@ -251,6 +333,31 @@ func (p *glPainter) glDrawTexture(texture Texture, alpha float32) {
logError()
}

func (p *glPainter) glDrawLine(width float32, col color.Color, feather float32) {
gl.UseProgram(uint32(p.lineProgram))

gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
logError()

colorUniform := gl.GetUniformLocation(uint32(p.lineProgram), gl.Str("color\x00"))
r, g, b, a := col.RGBA()
if a == 0 {
gl.Uniform4f(colorUniform, 0, 0, 0, 0)
} else {
alpha := float32(a)
col := []float32{float32(r) / alpha, float32(g) / alpha, float32(b) / alpha, alpha / 0xffff}
gl.Uniform4fv(colorUniform, 1, &col[0])
}
lineWidthUniform := gl.GetUniformLocation(uint32(p.lineProgram), gl.Str("lineWidth\x00"))
gl.Uniform1f(lineWidthUniform, width)

featherUniform := gl.GetUniformLocation(uint32(p.lineProgram), gl.Str("feather\x00"))
gl.Uniform1f(featherUniform, feather)

gl.DrawArrays(gl.TRIANGLES, 0, 6)
logError()
}

func (p *glPainter) glCapture(width, height int32, pixels *[]uint8) {
gl.ReadBuffer(gl.FRONT)
logError()
Expand Down

0 comments on commit 6af63b0

Please sign in to comment.