forked from fyne-io/fyne
-
Notifications
You must be signed in to change notification settings - Fork 0
/
painter.go
191 lines (164 loc) · 6.09 KB
/
painter.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
// Package gl provides a full Fyne render implementation using system OpenGL libraries.
package gl
import (
"fmt"
"github.com/gorustyt/fyne/v2/canvas3d/context"
"image"
"github.com/gorustyt/fyne/v2"
"github.com/gorustyt/fyne/v2/internal/driver"
"github.com/gorustyt/fyne/v2/theme"
)
func shaderSourceNamed(name string) ([]byte, []byte) {
switch name {
case "line":
return shaderLineVert.StaticContent, shaderLineFrag.StaticContent
case "line_es":
return shaderLineesVert.StaticContent, shaderLineesFrag.StaticContent
case "simple":
return shaderSimpleVert.StaticContent, shaderSimpleFrag.StaticContent
case "simple_es":
return shaderSimpleesVert.StaticContent, shaderSimpleesFrag.StaticContent
case "rectangle":
return shaderRectangleVert.StaticContent, shaderRectangleFrag.StaticContent
case "round_rectangle":
return shaderRectangleVert.StaticContent, shaderRoundrectangleFrag.StaticContent
case "rectangle_es":
return shaderRectangleesVert.StaticContent, shaderRectangleesFrag.StaticContent
case "round_rectangle_es":
return shaderRectangleesVert.StaticContent, shaderRoundrectangleesFrag.StaticContent
}
return nil, nil
}
// Painter defines the functionality of our OpenGL based renderer
type Painter interface {
// Init tell a new painter to initialise, usually called after a context is available
Init()
// Capture requests that the specified canvas be drawn to an in-memory image
Capture(fyne.Canvas) image.Image
// Clear tells our painter to prepare a fresh paint
Clear()
// Free is used to indicate that a certain canvas object is no longer needed
Free(fyne.CanvasObject)
// Paint a single fyne.CanvasObject but not its children.
Paint(fyne.CanvasObject, fyne.Position, fyne.Size)
// SetFrameBufferScale tells us when we have more than 1 framebuffer pixel for each output pixel
SetFrameBufferScale(float32)
// SetOutputSize is used to change the resolution of our output viewport
SetOutputSize(int, int)
// StartClipping tells us that the following paint actions should be clipped to the specified area.
StartClipping(fyne.Position, fyne.Size)
// StopClipping stops clipping paint actions.
StopClipping()
}
// NewPainter creates a new GL based renderer for the provided canvas.
// If it is a master painter it will also initialise OpenGL
func NewPainter(c fyne.Canvas, ctx driver.WithContext) Painter {
p := &painter{canvas: c, contextProvider: ctx}
p.SetFrameBufferScale(1.0)
return p
}
type painter struct {
canvas fyne.Canvas
ctx context.Context
contextProvider driver.WithContext
program context.Program
lineProgram context.Program
rectangleProgram context.Program
roundRectangleProgram context.Program
texScale float32
pixScale float32 // pre-calculate scale*texScale for each draw
}
// Declare conformity to Painter interface
var _ Painter = (*painter)(nil)
func (p *painter) Clear() {
r, g, b, a := theme.BackgroundColor().RGBA()
p.ctx.ClearColor(float32(r)/max16bit, float32(g)/max16bit, float32(b)/max16bit, float32(a)/max16bit)
p.ctx.Clear(bitColorBuffer | bitDepthBuffer)
p.logError()
}
func (p *painter) Free(obj fyne.CanvasObject) {
p.freeTexture(obj)
}
func (p *painter) Paint(obj fyne.CanvasObject, pos fyne.Position, frame fyne.Size) {
if obj.Visible() {
p.drawObject(obj, pos, frame)
}
}
func (p *painter) SetFrameBufferScale(scale float32) {
p.texScale = scale
p.pixScale = p.canvas.Scale() * p.texScale
}
func (p *painter) SetOutputSize(width, height int) {
p.ctx.Viewport(0, 0, width, height)
p.logError()
}
func (p *painter) StartClipping(pos fyne.Position, size fyne.Size) {
x := p.textureScale(pos.X)
y := p.textureScale(p.canvas.Size().Height - pos.Y - size.Height)
w := p.textureScale(size.Width)
h := p.textureScale(size.Height)
p.ctx.Scissor(int32(x), int32(y), int32(w), int32(h))
p.ctx.Enable(scissorTest)
p.logError()
}
func (p *painter) StopClipping() {
p.ctx.Disable(scissorTest)
p.logError()
}
func (p *painter) compileShader(source string, shaderType uint32) (context.Shader, error) {
shader := p.ctx.CreateShader(shaderType)
p.ctx.ShaderSource(shader, source)
p.logError()
p.ctx.CompileShader(shader)
p.logError()
info := p.ctx.GetShaderInfoLog(shader)
if p.ctx.GetShaderi(shader, compileStatus) == GlFalse {
return noShader, fmt.Errorf("failed to compile OpenGL shader:\n%s\n>>> SHADER SOURCE\n%s\n<<< SHADER SOURCE", info, source)
}
// The info is probably a null terminated string.
// An empty info has been seen as "\x00" or "\x00\x00".
if len(info) > 0 && info != "\x00" && info != "\x00\x00" {
fmt.Printf("OpenGL shader compilation output:\n%s\n>>> SHADER SOURCE\n%s\n<<< SHADER SOURCE\n", info, source)
}
return shader, nil
}
func (p *painter) createProgramWithShader(vertexSrc, fragmentSrc []byte) context.Program {
vertShader, err := p.compileShader(string(vertexSrc), vertexShader)
if err != nil {
panic(err)
}
fragShader, err := p.compileShader(string(fragmentSrc), fragmentShader)
if err != nil {
panic(err)
}
prog := p.ctx.CreateProgram()
p.ctx.AttachShader(prog, vertShader)
p.ctx.AttachShader(prog, fragShader)
p.ctx.LinkProgram(prog)
info := p.ctx.GetProgramInfoLog(prog)
if p.ctx.GetProgrami(prog, linkStatus) == GlFalse {
panic(fmt.Errorf("failed to link OpenGL program:\n%s", info))
}
// The info is probably a null terminated string.
// An empty info has been seen as "\x00" or "\x00\x00".
if len(info) > 0 && info != "\x00" && info != "\x00\x00" {
fmt.Printf("OpenGL program linking output:\n%s\n", info)
}
if glErr := p.ctx.GetError(); glErr != 0 {
panic(fmt.Sprintf("failed to link OpenGL program; error code: %x", glErr))
}
return prog
}
func (p *painter) createProgram(shaderFilename string) context.Program {
// Why a switch over a filename?
// Because this allows for a minimal change, once we reach Go 1.16 and use go:embed instead of
// fyne bundle.
vertexSrc, fragmentSrc := shaderSourceNamed(shaderFilename)
if vertexSrc == nil {
panic("shader not found: " + shaderFilename)
}
return p.createProgramWithShader(vertexSrc, fragmentSrc)
}
func (p *painter) logError() {
logGLError(p.ctx.GetError)
}