path.go raw
1 // SPDX-License-Identifier: Unlicense OR MIT
2
3 package gpu
4
5 // GPU accelerated path drawing using the algorithms from
6 // Pathfinder (https://github.com/servo/pathfinder).
7
8 import (
9 "encoding/binary"
10 "image"
11 "math"
12 "unsafe"
13
14 "github.com/p9c/p9/pkg/gel/gio/f32"
15 "github.com/p9c/p9/pkg/gel/gio/gpu/internal/driver"
16 "github.com/p9c/p9/pkg/gel/gio/internal/byteslice"
17 "github.com/p9c/p9/pkg/gel/gio/internal/f32color"
18 )
19
20 type pather struct {
21 ctx driver.Device
22
23 viewport image.Point
24
25 stenciler *stenciler
26 coverer *coverer
27 }
28
29 type coverer struct {
30 ctx driver.Device
31 prog [3]*program
32 texUniforms *coverTexUniforms
33 colUniforms *coverColUniforms
34 linearGradientUniforms *coverLinearGradientUniforms
35 layout driver.InputLayout
36 }
37
38 type coverTexUniforms struct {
39 vert struct {
40 coverUniforms
41 _ [12]byte // Padding to multiple of 16.
42 }
43 }
44
45 type coverColUniforms struct {
46 vert struct {
47 coverUniforms
48 _ [12]byte // Padding to multiple of 16.
49 }
50 frag struct {
51 colorUniforms
52 }
53 }
54
55 type coverLinearGradientUniforms struct {
56 vert struct {
57 coverUniforms
58 _ [12]byte // Padding to multiple of 16.
59 }
60 frag struct {
61 gradientUniforms
62 }
63 }
64
65 type coverUniforms struct {
66 transform [4]float32
67 uvCoverTransform [4]float32
68 uvTransformR1 [4]float32
69 uvTransformR2 [4]float32
70 z float32
71 }
72
73 type stenciler struct {
74 ctx driver.Device
75 prog struct {
76 prog *program
77 uniforms *stencilUniforms
78 layout driver.InputLayout
79 }
80 iprog struct {
81 prog *program
82 uniforms *intersectUniforms
83 layout driver.InputLayout
84 }
85 fbos fboSet
86 intersections fboSet
87 indexBuf driver.Buffer
88 }
89
90 type stencilUniforms struct {
91 vert struct {
92 transform [4]float32
93 pathOffset [2]float32
94 _ [8]byte // Padding to multiple of 16.
95 }
96 }
97
98 type intersectUniforms struct {
99 vert struct {
100 uvTransform [4]float32
101 subUVTransform [4]float32
102 }
103 }
104
105 type fboSet struct {
106 fbos []stencilFBO
107 }
108
109 type stencilFBO struct {
110 size image.Point
111 fbo driver.Framebuffer
112 tex driver.Texture
113 }
114
115 type pathData struct {
116 ncurves int
117 data driver.Buffer
118 }
119
120 // vertex data suitable for passing to vertex programs.
121 type vertex struct {
122 // Corner encodes the corner: +0.5 for south, +.25 for east.
123 Corner float32
124 MaxY float32
125 FromX, FromY float32
126 CtrlX, CtrlY float32
127 ToX, ToY float32
128 }
129
130 func (v vertex) encode(d []byte, maxy uint32) {
131 bo := binary.LittleEndian
132 bo.PutUint32(d[0:], math.Float32bits(v.Corner))
133 bo.PutUint32(d[4:], maxy)
134 bo.PutUint32(d[8:], math.Float32bits(v.FromX))
135 bo.PutUint32(d[12:], math.Float32bits(v.FromY))
136 bo.PutUint32(d[16:], math.Float32bits(v.CtrlX))
137 bo.PutUint32(d[20:], math.Float32bits(v.CtrlY))
138 bo.PutUint32(d[24:], math.Float32bits(v.ToX))
139 bo.PutUint32(d[28:], math.Float32bits(v.ToY))
140 }
141
142 const (
143 // Number of path quads per draw batch.
144 pathBatchSize = 10000
145 // Size of a vertex as sent to gpu
146 vertStride = 8 * 4
147 )
148
149 func newPather(ctx driver.Device) *pather {
150 return &pather{
151 ctx: ctx,
152 stenciler: newStenciler(ctx),
153 coverer: newCoverer(ctx),
154 }
155 }
156
157 func newCoverer(ctx driver.Device) *coverer {
158 c := &coverer{
159 ctx: ctx,
160 }
161 c.colUniforms = new(coverColUniforms)
162 c.texUniforms = new(coverTexUniforms)
163 c.linearGradientUniforms = new(coverLinearGradientUniforms)
164 prog, layout, err := createColorPrograms(ctx, shader_cover_vert, shader_cover_frag,
165 [3]interface{}{&c.colUniforms.vert, &c.linearGradientUniforms.vert, &c.texUniforms.vert},
166 [3]interface{}{&c.colUniforms.frag, &c.linearGradientUniforms.frag, nil},
167 )
168 if err != nil {
169 panic(err)
170 }
171 c.prog = prog
172 c.layout = layout
173 return c
174 }
175
176 func newStenciler(ctx driver.Device) *stenciler {
177 // Allocate a suitably large index buffer for drawing paths.
178 indices := make([]uint16, pathBatchSize*6)
179 for i := 0; i < pathBatchSize; i++ {
180 i := uint16(i)
181 indices[i*6+0] = i*4 + 0
182 indices[i*6+1] = i*4 + 1
183 indices[i*6+2] = i*4 + 2
184 indices[i*6+3] = i*4 + 2
185 indices[i*6+4] = i*4 + 1
186 indices[i*6+5] = i*4 + 3
187 }
188 indexBuf, err := ctx.NewImmutableBuffer(driver.BufferBindingIndices, byteslice.Slice(indices))
189 if err != nil {
190 panic(err)
191 }
192 progLayout, err := ctx.NewInputLayout(shader_stencil_vert, []driver.InputDesc{
193 {Type: driver.DataTypeFloat, Size: 1, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).Corner))},
194 {Type: driver.DataTypeFloat, Size: 1, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).MaxY))},
195 {Type: driver.DataTypeFloat, Size: 2, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).FromX))},
196 {Type: driver.DataTypeFloat, Size: 2, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).CtrlX))},
197 {Type: driver.DataTypeFloat, Size: 2, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).ToX))},
198 })
199 if err != nil {
200 panic(err)
201 }
202 iprogLayout, err := ctx.NewInputLayout(shader_intersect_vert, []driver.InputDesc{
203 {Type: driver.DataTypeFloat, Size: 2, Offset: 0},
204 {Type: driver.DataTypeFloat, Size: 2, Offset: 4 * 2},
205 })
206 if err != nil {
207 panic(err)
208 }
209 st := &stenciler{
210 ctx: ctx,
211 indexBuf: indexBuf,
212 }
213 prog, err := ctx.NewProgram(shader_stencil_vert, shader_stencil_frag)
214 if err != nil {
215 panic(err)
216 }
217 st.prog.uniforms = new(stencilUniforms)
218 vertUniforms := newUniformBuffer(ctx, &st.prog.uniforms.vert)
219 st.prog.prog = newProgram(prog, vertUniforms, nil)
220 st.prog.layout = progLayout
221 iprog, err := ctx.NewProgram(shader_intersect_vert, shader_intersect_frag)
222 if err != nil {
223 panic(err)
224 }
225 st.iprog.uniforms = new(intersectUniforms)
226 vertUniforms = newUniformBuffer(ctx, &st.iprog.uniforms.vert)
227 st.iprog.prog = newProgram(iprog, vertUniforms, nil)
228 st.iprog.layout = iprogLayout
229 return st
230 }
231
232 func (s *fboSet) resize(ctx driver.Device, sizes []image.Point) {
233 // Add fbos.
234 for i := len(s.fbos); i < len(sizes); i++ {
235 s.fbos = append(s.fbos, stencilFBO{})
236 }
237 // Resize fbos.
238 for i, sz := range sizes {
239 f := &s.fbos[i]
240 // Resizing or recreating FBOs can introduce rendering stalls.
241 // Avoid if the space waste is not too high.
242 resize := sz.X > f.size.X || sz.Y > f.size.Y
243 waste := float32(sz.X*sz.Y) / float32(f.size.X*f.size.Y)
244 resize = resize || waste > 1.2
245 if resize {
246 if f.fbo != nil {
247 f.fbo.Release()
248 f.tex.Release()
249 }
250 tex, err := ctx.NewTexture(driver.TextureFormatFloat, sz.X, sz.Y, driver.FilterNearest, driver.FilterNearest,
251 driver.BufferBindingTexture|driver.BufferBindingFramebuffer)
252 if err != nil {
253 panic(err)
254 }
255 fbo, err := ctx.NewFramebuffer(tex, 0)
256 if err != nil {
257 panic(err)
258 }
259 f.size = sz
260 f.tex = tex
261 f.fbo = fbo
262 }
263 }
264 // Delete extra fbos.
265 s.delete(ctx, len(sizes))
266 }
267
268 func (s *fboSet) invalidate(ctx driver.Device) {
269 for _, f := range s.fbos {
270 f.fbo.Invalidate()
271 }
272 }
273
274 func (s *fboSet) delete(ctx driver.Device, idx int) {
275 for i := idx; i < len(s.fbos); i++ {
276 f := s.fbos[i]
277 f.fbo.Release()
278 f.tex.Release()
279 }
280 s.fbos = s.fbos[:idx]
281 }
282
283 func (s *stenciler) release() {
284 s.fbos.delete(s.ctx, 0)
285 s.prog.layout.Release()
286 s.prog.prog.Release()
287 s.iprog.layout.Release()
288 s.iprog.prog.Release()
289 s.indexBuf.Release()
290 }
291
292 func (p *pather) release() {
293 p.stenciler.release()
294 p.coverer.release()
295 }
296
297 func (c *coverer) release() {
298 for _, p := range c.prog {
299 p.Release()
300 }
301 c.layout.Release()
302 }
303
304 func buildPath(ctx driver.Device, p []byte) pathData {
305 buf, err := ctx.NewImmutableBuffer(driver.BufferBindingVertices, p)
306 if err != nil {
307 panic(err)
308 }
309 return pathData{
310 ncurves: len(p) / vertStride,
311 data: buf,
312 }
313 }
314
315 func (p pathData) release() {
316 p.data.Release()
317 }
318
319 func (p *pather) begin(sizes []image.Point) {
320 p.stenciler.begin(sizes)
321 }
322
323 func (p *pather) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data pathData) {
324 p.stenciler.stencilPath(bounds, offset, uv, data)
325 }
326
327 func (s *stenciler) beginIntersect(sizes []image.Point) {
328 s.ctx.BlendFunc(driver.BlendFactorDstColor, driver.BlendFactorZero)
329 // 8 bit coverage is enough, but OpenGL ES only supports single channel
330 // floating point formats. Replace with GL_RGB+GL_UNSIGNED_BYTE if
331 // no floating point support is available.
332 s.intersections.resize(s.ctx, sizes)
333 s.ctx.BindProgram(s.iprog.prog.prog)
334 }
335
336 func (s *stenciler) invalidateFBO() {
337 s.intersections.invalidate(s.ctx)
338 s.fbos.invalidate(s.ctx)
339 }
340
341 func (s *stenciler) cover(idx int) stencilFBO {
342 return s.fbos.fbos[idx]
343 }
344
345 func (s *stenciler) begin(sizes []image.Point) {
346 s.ctx.BlendFunc(driver.BlendFactorOne, driver.BlendFactorOne)
347 s.fbos.resize(s.ctx, sizes)
348 s.ctx.BindProgram(s.prog.prog.prog)
349 s.ctx.BindInputLayout(s.prog.layout)
350 s.ctx.BindIndexBuffer(s.indexBuf)
351 }
352
353 func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data pathData) {
354 s.ctx.Viewport(uv.X, uv.Y, bounds.Dx(), bounds.Dy())
355 // Transform UI coordinates to OpenGL coordinates.
356 texSize := f32.Point{X: float32(bounds.Dx()), Y: float32(bounds.Dy())}
357 scale := f32.Point{X: 2 / texSize.X, Y: 2 / texSize.Y}
358 orig := f32.Point{X: -1 - float32(bounds.Min.X)*2/texSize.X, Y: -1 - float32(bounds.Min.Y)*2/texSize.Y}
359 s.prog.uniforms.vert.transform = [4]float32{scale.X, scale.Y, orig.X, orig.Y}
360 s.prog.uniforms.vert.pathOffset = [2]float32{offset.X, offset.Y}
361 s.prog.prog.UploadUniforms()
362 // Draw in batches that fit in uint16 indices.
363 start := 0
364 nquads := data.ncurves / 4
365 for start < nquads {
366 batch := nquads - start
367 if max := pathBatchSize; batch > max {
368 batch = max
369 }
370 off := vertStride * start * 4
371 s.ctx.BindVertexBuffer(data.data, vertStride, off)
372 s.ctx.DrawElements(driver.DrawModeTriangles, 0, batch*6)
373 start += batch
374 }
375 }
376
377 func (p *pather) cover(z float32, mat materialType, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) {
378 p.coverer.cover(z, mat, col, col1, col2, scale, off, uvTrans, coverScale, coverOff)
379 }
380
381 func (c *coverer) cover(z float32, mat materialType, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) {
382 p := c.prog[mat]
383 c.ctx.BindProgram(p.prog)
384 var uniforms *coverUniforms
385 switch mat {
386 case materialColor:
387 c.colUniforms.frag.color = col
388 uniforms = &c.colUniforms.vert.coverUniforms
389 case materialLinearGradient:
390 c.linearGradientUniforms.frag.color1 = col1
391 c.linearGradientUniforms.frag.color2 = col2
392
393 t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
394 c.linearGradientUniforms.vert.uvTransformR1 = [4]float32{t1, t2, t3, 0}
395 c.linearGradientUniforms.vert.uvTransformR2 = [4]float32{t4, t5, t6, 0}
396 uniforms = &c.linearGradientUniforms.vert.coverUniforms
397 case materialTexture:
398 t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
399 c.texUniforms.vert.uvTransformR1 = [4]float32{t1, t2, t3, 0}
400 c.texUniforms.vert.uvTransformR2 = [4]float32{t4, t5, t6, 0}
401 uniforms = &c.texUniforms.vert.coverUniforms
402 }
403 uniforms.z = z
404 uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
405 uniforms.uvCoverTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y}
406 p.UploadUniforms()
407 c.ctx.DrawArrays(driver.DrawModeTriangleStrip, 0, 4)
408 }
409
410 func init() {
411 // Check that struct vertex has the expected size and
412 // that it contains no padding.
413 if unsafe.Sizeof(*(*vertex)(nil)) != vertStride {
414 panic("unexpected struct size")
415 }
416 }
417