123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424 |
- // SPDX-License-Identifier: Unlicense OR MIT
- package gpu
- // GPU accelerated path drawing using the algorithms from
- // Pathfinder (https://github.com/servo/pathfinder).
- import (
- "encoding/binary"
- "image"
- "math"
- "unsafe"
- "gioui.org/gpu/internal/driver"
- "gioui.org/internal/byteslice"
- "gioui.org/internal/f32"
- "gioui.org/internal/f32color"
- "gioui.org/shader"
- "gioui.org/shader/gio"
- )
- type pather struct {
- ctx driver.Device
- viewport image.Point
- stenciler *stenciler
- coverer *coverer
- }
- type coverer struct {
- ctx driver.Device
- pipelines [2][3]*pipeline
- texUniforms *coverTexUniforms
- colUniforms *coverColUniforms
- linearGradientUniforms *coverLinearGradientUniforms
- }
- type coverTexUniforms struct {
- coverUniforms
- _ [12]byte // Padding to multiple of 16.
- }
- type coverColUniforms struct {
- coverUniforms
- _ [128 - unsafe.Sizeof(coverUniforms{}) - unsafe.Sizeof(colorUniforms{})]byte // Padding to 128 bytes.
- colorUniforms
- }
- type coverLinearGradientUniforms struct {
- coverUniforms
- _ [128 - unsafe.Sizeof(coverUniforms{}) - unsafe.Sizeof(gradientUniforms{})]byte // Padding to 128.
- gradientUniforms
- }
- type coverUniforms struct {
- transform [4]float32
- uvCoverTransform [4]float32
- uvTransformR1 [4]float32
- uvTransformR2 [4]float32
- fbo float32
- }
- type stenciler struct {
- ctx driver.Device
- pipeline struct {
- pipeline *pipeline
- uniforms *stencilUniforms
- }
- ipipeline struct {
- pipeline *pipeline
- uniforms *intersectUniforms
- }
- fbos fboSet
- intersections fboSet
- indexBuf driver.Buffer
- }
- type stencilUniforms struct {
- transform [4]float32
- pathOffset [2]float32
- _ [8]byte // Padding to multiple of 16.
- }
- type intersectUniforms struct {
- vert struct {
- uvTransform [4]float32
- subUVTransform [4]float32
- }
- }
- type fboSet struct {
- fbos []FBO
- }
- type FBO struct {
- size image.Point
- tex driver.Texture
- }
- type pathData struct {
- ncurves int
- data driver.Buffer
- }
- // vertex data suitable for passing to vertex programs.
- type vertex struct {
- // Corner encodes the corner: +0.5 for south, +.25 for east.
- Corner float32
- MaxY float32
- FromX, FromY float32
- CtrlX, CtrlY float32
- ToX, ToY float32
- }
- // encode needs to stay in-sync with the code in clip.go encodeQuadTo.
- func (v vertex) encode(d []byte, maxy uint32) {
- d = d[0:32]
- bo := binary.LittleEndian
- bo.PutUint32(d[0:4], math.Float32bits(v.Corner))
- bo.PutUint32(d[4:8], maxy)
- bo.PutUint32(d[8:12], math.Float32bits(v.FromX))
- bo.PutUint32(d[12:16], math.Float32bits(v.FromY))
- bo.PutUint32(d[16:20], math.Float32bits(v.CtrlX))
- bo.PutUint32(d[20:24], math.Float32bits(v.CtrlY))
- bo.PutUint32(d[24:28], math.Float32bits(v.ToX))
- bo.PutUint32(d[28:32], math.Float32bits(v.ToY))
- }
- const (
- // Number of path quads per draw batch.
- pathBatchSize = 10000
- // Size of a vertex as sent to gpu
- vertStride = 8 * 4
- )
- func newPather(ctx driver.Device) *pather {
- return &pather{
- ctx: ctx,
- stenciler: newStenciler(ctx),
- coverer: newCoverer(ctx),
- }
- }
- func newCoverer(ctx driver.Device) *coverer {
- c := &coverer{
- ctx: ctx,
- }
- c.colUniforms = new(coverColUniforms)
- c.texUniforms = new(coverTexUniforms)
- c.linearGradientUniforms = new(coverLinearGradientUniforms)
- pipelines, err := createColorPrograms(ctx, gio.Shader_cover_vert, gio.Shader_cover_frag,
- [3]interface{}{c.colUniforms, c.linearGradientUniforms, c.texUniforms},
- )
- if err != nil {
- panic(err)
- }
- c.pipelines = pipelines
- return c
- }
- func newStenciler(ctx driver.Device) *stenciler {
- // Allocate a suitably large index buffer for drawing paths.
- indices := make([]uint16, pathBatchSize*6)
- for i := 0; i < pathBatchSize; i++ {
- i := uint16(i)
- indices[i*6+0] = i*4 + 0
- indices[i*6+1] = i*4 + 1
- indices[i*6+2] = i*4 + 2
- indices[i*6+3] = i*4 + 2
- indices[i*6+4] = i*4 + 1
- indices[i*6+5] = i*4 + 3
- }
- indexBuf, err := ctx.NewImmutableBuffer(driver.BufferBindingIndices, byteslice.Slice(indices))
- if err != nil {
- panic(err)
- }
- progLayout := driver.VertexLayout{
- Inputs: []driver.InputDesc{
- {Type: shader.DataTypeFloat, Size: 1, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).Corner))},
- {Type: shader.DataTypeFloat, Size: 1, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).MaxY))},
- {Type: shader.DataTypeFloat, Size: 2, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).FromX))},
- {Type: shader.DataTypeFloat, Size: 2, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).CtrlX))},
- {Type: shader.DataTypeFloat, Size: 2, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).ToX))},
- },
- Stride: vertStride,
- }
- iprogLayout := driver.VertexLayout{
- Inputs: []driver.InputDesc{
- {Type: shader.DataTypeFloat, Size: 2, Offset: 0},
- {Type: shader.DataTypeFloat, Size: 2, Offset: 4 * 2},
- },
- Stride: 4 * 4,
- }
- st := &stenciler{
- ctx: ctx,
- indexBuf: indexBuf,
- }
- vsh, fsh, err := newShaders(ctx, gio.Shader_stencil_vert, gio.Shader_stencil_frag)
- if err != nil {
- panic(err)
- }
- defer vsh.Release()
- defer fsh.Release()
- st.pipeline.uniforms = new(stencilUniforms)
- vertUniforms := newUniformBuffer(ctx, st.pipeline.uniforms)
- pipe, err := st.ctx.NewPipeline(driver.PipelineDesc{
- VertexShader: vsh,
- FragmentShader: fsh,
- VertexLayout: progLayout,
- BlendDesc: driver.BlendDesc{
- Enable: true,
- SrcFactor: driver.BlendFactorOne,
- DstFactor: driver.BlendFactorOne,
- },
- PixelFormat: driver.TextureFormatFloat,
- Topology: driver.TopologyTriangles,
- })
- st.pipeline.pipeline = &pipeline{pipe, vertUniforms}
- if err != nil {
- panic(err)
- }
- vsh, fsh, err = newShaders(ctx, gio.Shader_intersect_vert, gio.Shader_intersect_frag)
- if err != nil {
- panic(err)
- }
- defer vsh.Release()
- defer fsh.Release()
- st.ipipeline.uniforms = new(intersectUniforms)
- vertUniforms = newUniformBuffer(ctx, &st.ipipeline.uniforms.vert)
- ipipe, err := st.ctx.NewPipeline(driver.PipelineDesc{
- VertexShader: vsh,
- FragmentShader: fsh,
- VertexLayout: iprogLayout,
- BlendDesc: driver.BlendDesc{
- Enable: true,
- SrcFactor: driver.BlendFactorDstColor,
- DstFactor: driver.BlendFactorZero,
- },
- PixelFormat: driver.TextureFormatFloat,
- Topology: driver.TopologyTriangleStrip,
- })
- st.ipipeline.pipeline = &pipeline{ipipe, vertUniforms}
- if err != nil {
- panic(err)
- }
- return st
- }
- func (s *fboSet) resize(ctx driver.Device, format driver.TextureFormat, sizes []image.Point) {
- // Add fbos.
- for i := len(s.fbos); i < len(sizes); i++ {
- s.fbos = append(s.fbos, FBO{})
- }
- // Resize fbos.
- for i, sz := range sizes {
- f := &s.fbos[i]
- // Resizing or recreating FBOs can introduce rendering stalls.
- // Avoid if the space waste is not too high.
- resize := sz.X > f.size.X || sz.Y > f.size.Y
- waste := float32(sz.X*sz.Y) / float32(f.size.X*f.size.Y)
- resize = resize || waste > 1.2
- if resize {
- if f.tex != nil {
- f.tex.Release()
- }
- // Add 5% extra space in each dimension to minimize resizing.
- sz = sz.Mul(105).Div(100)
- max := ctx.Caps().MaxTextureSize
- if sz.Y > max {
- sz.Y = max
- }
- if sz.X > max {
- sz.X = max
- }
- tex, err := ctx.NewTexture(format, sz.X, sz.Y, driver.FilterNearest, driver.FilterNearest,
- driver.BufferBindingTexture|driver.BufferBindingFramebuffer)
- if err != nil {
- panic(err)
- }
- f.size = sz
- f.tex = tex
- }
- }
- // Delete extra fbos.
- s.delete(ctx, len(sizes))
- }
- func (s *fboSet) delete(ctx driver.Device, idx int) {
- for i := idx; i < len(s.fbos); i++ {
- f := s.fbos[i]
- f.tex.Release()
- }
- s.fbos = s.fbos[:idx]
- }
- func (s *stenciler) release() {
- s.fbos.delete(s.ctx, 0)
- s.intersections.delete(s.ctx, 0)
- s.pipeline.pipeline.Release()
- s.ipipeline.pipeline.Release()
- s.indexBuf.Release()
- }
- func (p *pather) release() {
- p.stenciler.release()
- p.coverer.release()
- }
- func (c *coverer) release() {
- for _, p := range c.pipelines {
- for _, p := range p {
- p.Release()
- }
- }
- }
- func buildPath(ctx driver.Device, p []byte) pathData {
- buf, err := ctx.NewImmutableBuffer(driver.BufferBindingVertices, p)
- if err != nil {
- panic(err)
- }
- return pathData{
- ncurves: len(p) / vertStride,
- data: buf,
- }
- }
- func (p pathData) release() {
- p.data.Release()
- }
- func (p *pather) begin(sizes []image.Point) {
- p.stenciler.begin(sizes)
- }
- func (p *pather) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data pathData) {
- p.stenciler.stencilPath(bounds, offset, uv, data)
- }
- func (s *stenciler) beginIntersect(sizes []image.Point) {
- // 8 bit coverage is enough, but OpenGL ES only supports single channel
- // floating point formats. Replace with GL_RGB+GL_UNSIGNED_BYTE if
- // no floating point support is available.
- s.intersections.resize(s.ctx, driver.TextureFormatFloat, sizes)
- }
- func (s *stenciler) cover(idx int) FBO {
- return s.fbos.fbos[idx]
- }
- func (s *stenciler) begin(sizes []image.Point) {
- s.fbos.resize(s.ctx, driver.TextureFormatFloat, sizes)
- }
- func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data pathData) {
- s.ctx.Viewport(uv.X, uv.Y, bounds.Dx(), bounds.Dy())
- // Transform UI coordinates to OpenGL coordinates.
- texSize := f32.Point{X: float32(bounds.Dx()), Y: float32(bounds.Dy())}
- scale := f32.Point{X: 2 / texSize.X, Y: 2 / texSize.Y}
- orig := f32.Point{X: -1 - float32(bounds.Min.X)*2/texSize.X, Y: -1 - float32(bounds.Min.Y)*2/texSize.Y}
- s.pipeline.uniforms.transform = [4]float32{scale.X, scale.Y, orig.X, orig.Y}
- s.pipeline.uniforms.pathOffset = [2]float32{offset.X, offset.Y}
- s.pipeline.pipeline.UploadUniforms(s.ctx)
- // Draw in batches that fit in uint16 indices.
- start := 0
- nquads := data.ncurves / 4
- for start < nquads {
- batch := nquads - start
- if max := pathBatchSize; batch > max {
- batch = max
- }
- off := vertStride * start * 4
- s.ctx.BindVertexBuffer(data.data, off)
- s.ctx.DrawElements(0, batch*6)
- start += batch
- }
- }
- func (p *pather) cover(mat materialType, isFBO bool, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) {
- p.coverer.cover(mat, isFBO, col, col1, col2, scale, off, uvTrans, coverScale, coverOff)
- }
- func (c *coverer) cover(mat materialType, isFBO bool, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) {
- var uniforms *coverUniforms
- switch mat {
- case materialColor:
- c.colUniforms.color = col
- uniforms = &c.colUniforms.coverUniforms
- case materialLinearGradient:
- c.linearGradientUniforms.color1 = col1
- c.linearGradientUniforms.color2 = col2
- t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
- c.linearGradientUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
- c.linearGradientUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
- uniforms = &c.linearGradientUniforms.coverUniforms
- case materialTexture:
- t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
- c.texUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
- c.texUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
- uniforms = &c.texUniforms.coverUniforms
- }
- uniforms.fbo = 0
- if isFBO {
- uniforms.fbo = 1
- }
- uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
- uniforms.uvCoverTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y}
- fboIdx := 0
- if isFBO {
- fboIdx = 1
- }
- c.pipelines[fboIdx][mat].UploadUniforms(c.ctx)
- c.ctx.DrawArrays(0, 4)
- }
- func init() {
- // Check that struct vertex has the expected size and
- // that it contains no padding.
- if unsafe.Sizeof(*(*vertex)(nil)) != vertStride {
- panic("unexpected struct size")
- }
- }
|