mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-06 09:55:40 +00:00
gpu, op, internal/ops: add affine transformations
Add support for affine transformations. The key changes are outlined below. - Painting/clipping with rectangles is handled by, for complex transforms, creating clipping paths representing the transformed rectangle and using a larger bounding box. Cover/Blit shaders updated correspondingly to correctly map texture cordinates from the new bounding boxes. - Since path splitting must happen on CPU the transforms must happen CPU side as well - offsets removed from shaders. - Complex transforms will lead to different path splitting which means that GPU arrays can no longer be cached if the transform has changed. Thus the current transform is added as a key to the cache. - Add a public API to op for setting Affine transformations. There are a number of optimizations that could be explored further but which are left out now: - Caching also of CPU operations (e.g path splitting & transforms) and not only caching the GPU arrays. - Allow for re-use of cached GPU vertices if the transformation change is a pure offset / scaling since the splitting is then the same. Signed-off-by: Viktor <viktor.ogeman@gmail.com>
This commit is contained in:
+145
-49
@@ -61,10 +61,11 @@ type drawOps struct {
|
|||||||
// zimageOps are the rectangle clipped opaque images
|
// zimageOps are the rectangle clipped opaque images
|
||||||
// that can use fast front-to-back rendering with z-test
|
// that can use fast front-to-back rendering with z-test
|
||||||
// and no blending.
|
// and no blending.
|
||||||
zimageOps []imageOp
|
zimageOps []imageOp
|
||||||
pathOps []*pathOp
|
pathOps []*pathOp
|
||||||
pathOpCache []pathOp
|
pathOpCache []pathOp
|
||||||
qs quadSplitter
|
qs quadSplitter
|
||||||
|
uniqueKeyCounter int
|
||||||
}
|
}
|
||||||
|
|
||||||
type drawState struct {
|
type drawState struct {
|
||||||
@@ -82,7 +83,6 @@ type drawState struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type pathOp struct {
|
type pathOp struct {
|
||||||
off f32.Point
|
|
||||||
// clip is the union of all
|
// clip is the union of all
|
||||||
// later clip rectangles.
|
// later clip rectangles.
|
||||||
clip image.Rectangle
|
clip image.Rectangle
|
||||||
@@ -96,7 +96,6 @@ type pathOp struct {
|
|||||||
type imageOp struct {
|
type imageOp struct {
|
||||||
z float32
|
z float32
|
||||||
path *pathOp
|
path *pathOp
|
||||||
off f32.Point
|
|
||||||
clip image.Rectangle
|
clip image.Rectangle
|
||||||
material material
|
material material
|
||||||
clipType clipType
|
clipType clipType
|
||||||
@@ -109,9 +108,8 @@ type material struct {
|
|||||||
// For materialTypeColor.
|
// For materialTypeColor.
|
||||||
color f32color.RGBA
|
color f32color.RGBA
|
||||||
// For materialTypeTexture.
|
// For materialTypeTexture.
|
||||||
texture *texture
|
texture *texture
|
||||||
uvScale f32.Point
|
uvTrans f32.Affine2D
|
||||||
uvOffset f32.Point
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// clipOp is the shadow of clip.Op.
|
// clipOp is the shadow of clip.Op.
|
||||||
@@ -227,7 +225,7 @@ type blitter struct {
|
|||||||
type blitColUniforms struct {
|
type blitColUniforms struct {
|
||||||
vert struct {
|
vert struct {
|
||||||
blitUniforms
|
blitUniforms
|
||||||
_ [10]byte // Padding to a multiple of 16.
|
_ [12]byte // Padding to a multiple of 16.
|
||||||
}
|
}
|
||||||
frag struct {
|
frag struct {
|
||||||
colorUniforms
|
colorUniforms
|
||||||
@@ -237,7 +235,7 @@ type blitColUniforms struct {
|
|||||||
type blitTexUniforms struct {
|
type blitTexUniforms struct {
|
||||||
vert struct {
|
vert struct {
|
||||||
blitUniforms
|
blitUniforms
|
||||||
_ [10]byte // Padding to a multiple of 16.
|
_ [12]byte // Padding to a multiple of 16.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,9 +251,10 @@ type program struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type blitUniforms struct {
|
type blitUniforms struct {
|
||||||
transform [4]float32
|
transform [4]float32
|
||||||
uvTransform [4]float32
|
uvTransformR1 [4]float32
|
||||||
z float32
|
uvTransformR2 [4]float32
|
||||||
|
z float32
|
||||||
}
|
}
|
||||||
|
|
||||||
type colorUniforms struct {
|
type colorUniforms struct {
|
||||||
@@ -507,7 +506,7 @@ func (r *renderer) stencilClips(pathCache *opCache, ops []*pathOp) {
|
|||||||
r.ctx.Clear(0.0, 0.0, 0.0, 0.0)
|
r.ctx.Clear(0.0, 0.0, 0.0, 0.0)
|
||||||
}
|
}
|
||||||
data, _ := pathCache.get(p.pathKey)
|
data, _ := pathCache.get(p.pathKey)
|
||||||
r.pather.stencilPath(p.clip, p.off, p.place.Pos, data.(*pathData))
|
r.pather.stencilPath(p.clip, p.place.Pos, data.(*pathData))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -682,6 +681,28 @@ func (d *drawOps) newPathOp() *pathOp {
|
|||||||
return &d.pathOpCache[len(d.pathOpCache)-1]
|
return &d.pathOpCache[len(d.pathOpCache)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey ops.Key) {
|
||||||
|
npath := d.newPathOp()
|
||||||
|
*npath = pathOp{
|
||||||
|
parent: state.cpath,
|
||||||
|
}
|
||||||
|
state.cpath = npath
|
||||||
|
if len(aux) > 0 {
|
||||||
|
state.rect = false
|
||||||
|
state.cpath.pathKey = auxKey
|
||||||
|
state.cpath.path = true
|
||||||
|
state.cpath.pathVerts = aux
|
||||||
|
d.pathOps = append(d.pathOps, state.cpath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// noCacheKey creates a new key for caches, but one that is unique and
|
||||||
|
// thus will never lead to re-use.
|
||||||
|
func (d *drawOps) noCacheKey() ops.Key {
|
||||||
|
d.uniqueKeyCounter--
|
||||||
|
return d.reader.NewKey(d.uniqueKeyCounter)
|
||||||
|
}
|
||||||
|
|
||||||
func (d *drawOps) collectOps(r *ops.Reader, state drawState) int {
|
func (d *drawOps) collectOps(r *ops.Reader, state drawState) int {
|
||||||
var aux []byte
|
var aux []byte
|
||||||
var auxKey ops.Key
|
var auxKey ops.Key
|
||||||
@@ -699,32 +720,23 @@ loop:
|
|||||||
case opconst.TypeClip:
|
case opconst.TypeClip:
|
||||||
var op clipOp
|
var op clipOp
|
||||||
op.decode(encOp.Data)
|
op.decode(encOp.Data)
|
||||||
off := state.t.Transform(f32.Point{})
|
|
||||||
|
|
||||||
bounds := op.bounds
|
bounds := op.bounds
|
||||||
if len(aux) > 0 {
|
if len(aux) > 0 {
|
||||||
// there is a clipping path, bounds is not filled before for performance
|
// There is a clipping path, build the gpu data and update the
|
||||||
aux, bounds = d.buildVerts(aux)
|
// cache key such that it will be equal only if the transform is the
|
||||||
|
// same also.
|
||||||
|
aux, op.bounds = d.buildVerts(aux, state.t)
|
||||||
|
auxKey = auxKey.SetTransform(state.t)
|
||||||
|
} else {
|
||||||
|
aux, op.bounds, _ = d.boundsForTransformedRect(bounds, state.t)
|
||||||
|
auxKey = d.noCacheKey()
|
||||||
}
|
}
|
||||||
state.clip = state.clip.Intersect(bounds.Add(off))
|
state.clip = state.clip.Intersect(op.bounds)
|
||||||
if state.clip.Empty() {
|
if state.clip.Empty() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
npath := d.newPathOp()
|
d.addClipPath(&state, aux, auxKey)
|
||||||
*npath = pathOp{
|
|
||||||
parent: state.cpath,
|
|
||||||
off: off,
|
|
||||||
}
|
|
||||||
state.cpath = npath
|
|
||||||
|
|
||||||
if len(aux) > 0 {
|
|
||||||
state.rect = false
|
|
||||||
state.cpath.pathKey = auxKey
|
|
||||||
state.cpath.path = true
|
|
||||||
state.cpath.pathVerts = aux
|
|
||||||
d.pathOps = append(d.pathOps, state.cpath)
|
|
||||||
}
|
|
||||||
aux = nil
|
aux = nil
|
||||||
auxKey = ops.Key{}
|
auxKey = ops.Key{}
|
||||||
case opconst.TypeColor:
|
case opconst.TypeColor:
|
||||||
@@ -735,13 +747,25 @@ loop:
|
|||||||
state.image = decodeImageOp(encOp.Data, encOp.Refs)
|
state.image = decodeImageOp(encOp.Data, encOp.Refs)
|
||||||
case opconst.TypePaint:
|
case opconst.TypePaint:
|
||||||
op := decodePaintOp(encOp.Data)
|
op := decodePaintOp(encOp.Data)
|
||||||
off := state.t.Transform(f32.Point{})
|
// Transform (if needed) the painting rectangle and if so generate a clip path,
|
||||||
clip := state.clip.Intersect(op.Rect.Add(off))
|
// for those cases also compute a partialTrans that maps texture coordinates between
|
||||||
|
// the new bounding rectangle and the transformed original paint rectangle.
|
||||||
|
clipData, bnd, partialTrans := d.boundsForTransformedRect(op.Rect, state.t)
|
||||||
|
clip := state.clip.Intersect(bnd).Canon()
|
||||||
if clip.Empty() {
|
if clip.Empty() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wasrect := state.rect
|
||||||
|
if clipData != nil {
|
||||||
|
// The paint operation is sheared or rotated, add a clip path representing
|
||||||
|
// this transformed rectangle.
|
||||||
|
d.addClipPath(&state, clipData, d.noCacheKey())
|
||||||
|
}
|
||||||
|
|
||||||
bounds := boundRectF(clip)
|
bounds := boundRectF(clip)
|
||||||
mat := state.materialFor(d.cache, op.Rect, off, bounds)
|
mat := state.materialFor(d.cache, bnd, partialTrans, bounds)
|
||||||
|
|
||||||
if bounds.Min == (image.Point{}) && bounds.Max == d.viewport && state.rect && mat.opaque && mat.material == materialColor {
|
if bounds.Min == (image.Point{}) && bounds.Max == d.viewport && state.rect && mat.opaque && mat.material == materialColor {
|
||||||
// The image is a uniform opaque color and takes up the whole screen.
|
// The image is a uniform opaque color and takes up the whole screen.
|
||||||
// Scrap images up to and including this image and set clear color.
|
// Scrap images up to and including this image and set clear color.
|
||||||
@@ -763,15 +787,20 @@ loop:
|
|||||||
img := imageOp{
|
img := imageOp{
|
||||||
z: zf,
|
z: zf,
|
||||||
path: state.cpath,
|
path: state.cpath,
|
||||||
off: off,
|
|
||||||
clip: bounds,
|
clip: bounds,
|
||||||
material: mat,
|
material: mat,
|
||||||
}
|
}
|
||||||
|
|
||||||
if state.rect && img.material.opaque {
|
if state.rect && img.material.opaque {
|
||||||
d.zimageOps = append(d.zimageOps, img)
|
d.zimageOps = append(d.zimageOps, img)
|
||||||
} else {
|
} else {
|
||||||
d.imageOps = append(d.imageOps, img)
|
d.imageOps = append(d.imageOps, img)
|
||||||
}
|
}
|
||||||
|
if clipData != nil {
|
||||||
|
// we added a clip path that should not remain
|
||||||
|
state.cpath = state.cpath.parent
|
||||||
|
state.rect = wasrect
|
||||||
|
}
|
||||||
case opconst.TypePush:
|
case opconst.TypePush:
|
||||||
state.z = d.collectOps(r, state)
|
state.z = d.collectOps(r, state)
|
||||||
case opconst.TypePop:
|
case opconst.TypePop:
|
||||||
@@ -792,7 +821,7 @@ func expandPathOp(p *pathOp, clip image.Rectangle) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *drawState) materialFor(cache *resourceCache, rect f32.Rectangle, off f32.Point, clip image.Rectangle) material {
|
func (d *drawState) materialFor(cache *resourceCache, rect f32.Rectangle, trans f32.Affine2D, clip image.Rectangle) material {
|
||||||
var m material
|
var m material
|
||||||
switch d.matType {
|
switch d.matType {
|
||||||
case materialColor:
|
case materialColor:
|
||||||
@@ -801,7 +830,7 @@ func (d *drawState) materialFor(cache *resourceCache, rect f32.Rectangle, off f3
|
|||||||
m.opaque = m.color.A == 1.0
|
m.opaque = m.color.A == 1.0
|
||||||
case materialTexture:
|
case materialTexture:
|
||||||
m.material = materialTexture
|
m.material = materialTexture
|
||||||
dr := boundRectF(rect.Add(off))
|
dr := boundRectF(rect)
|
||||||
sz := d.image.src.Bounds().Size()
|
sz := d.image.src.Bounds().Size()
|
||||||
sr := layout.FRect(d.image.rect)
|
sr := layout.FRect(d.image.rect)
|
||||||
if dx := float32(dr.Dx()); dx != 0 {
|
if dx := float32(dr.Dx()); dx != 0 {
|
||||||
@@ -827,7 +856,8 @@ func (d *drawState) materialFor(cache *resourceCache, rect f32.Rectangle, off f3
|
|||||||
tex = t
|
tex = t
|
||||||
}
|
}
|
||||||
m.texture = tex.(*texture)
|
m.texture = tex.(*texture)
|
||||||
m.uvScale, m.uvOffset = texSpaceTransform(sr, sz)
|
uvScale, uvOffset := texSpaceTransform(sr, sz)
|
||||||
|
m.uvTrans = trans.Mul(f32.Affine2D{}.Scale(f32.Point{}, uvScale).Offset(uvOffset))
|
||||||
}
|
}
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
@@ -846,7 +876,7 @@ func (r *renderer) drawZOps(ops []imageOp) {
|
|||||||
}
|
}
|
||||||
drc := img.clip
|
drc := img.clip
|
||||||
scale, off := clipSpaceTransform(drc, r.blitter.viewport)
|
scale, off := clipSpaceTransform(drc, r.blitter.viewport)
|
||||||
r.blitter.blit(img.z, m.material, m.color, scale, off, m.uvScale, m.uvOffset)
|
r.blitter.blit(img.z, m.material, m.color, scale, off, m.uvTrans)
|
||||||
}
|
}
|
||||||
r.ctx.SetDepthTest(false)
|
r.ctx.SetDepthTest(false)
|
||||||
}
|
}
|
||||||
@@ -865,11 +895,12 @@ func (r *renderer) drawOps(ops []imageOp) {
|
|||||||
r.ctx.BindTexture(0, r.texHandle(m.texture))
|
r.ctx.BindTexture(0, r.texHandle(m.texture))
|
||||||
}
|
}
|
||||||
drc := img.clip
|
drc := img.clip
|
||||||
|
|
||||||
scale, off := clipSpaceTransform(drc, r.blitter.viewport)
|
scale, off := clipSpaceTransform(drc, r.blitter.viewport)
|
||||||
var fbo stencilFBO
|
var fbo stencilFBO
|
||||||
switch img.clipType {
|
switch img.clipType {
|
||||||
case clipTypeNone:
|
case clipTypeNone:
|
||||||
r.blitter.blit(img.z, m.material, m.color, scale, off, m.uvScale, m.uvOffset)
|
r.blitter.blit(img.z, m.material, m.color, scale, off, m.uvTrans)
|
||||||
continue
|
continue
|
||||||
case clipTypePath:
|
case clipTypePath:
|
||||||
fbo = r.pather.stenciler.cover(img.place.Idx)
|
fbo = r.pather.stenciler.cover(img.place.Idx)
|
||||||
@@ -885,13 +916,13 @@ func (r *renderer) drawOps(ops []imageOp) {
|
|||||||
Max: img.place.Pos.Add(drc.Size()),
|
Max: img.place.Pos.Add(drc.Size()),
|
||||||
}
|
}
|
||||||
coverScale, coverOff := texSpaceTransform(toRectF(uv), fbo.size)
|
coverScale, coverOff := texSpaceTransform(toRectF(uv), fbo.size)
|
||||||
r.pather.cover(img.z, m.material, m.color, scale, off, m.uvScale, m.uvOffset, coverScale, coverOff)
|
r.pather.cover(img.z, m.material, m.color, scale, off, m.uvTrans, coverScale, coverOff)
|
||||||
}
|
}
|
||||||
r.ctx.DepthMask(true)
|
r.ctx.DepthMask(true)
|
||||||
r.ctx.SetDepthTest(false)
|
r.ctx.SetDepthTest(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *blitter) blit(z float32, mat materialType, col f32color.RGBA, scale, off, uvScale, uvOff f32.Point) {
|
func (b *blitter) blit(z float32, mat materialType, col f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D) {
|
||||||
p := b.prog[mat]
|
p := b.prog[mat]
|
||||||
b.ctx.BindProgram(p.prog)
|
b.ctx.BindProgram(p.prog)
|
||||||
var uniforms *blitUniforms
|
var uniforms *blitUniforms
|
||||||
@@ -900,7 +931,9 @@ func (b *blitter) blit(z float32, mat materialType, col f32color.RGBA, scale, of
|
|||||||
b.colUniforms.frag.color = col
|
b.colUniforms.frag.color = col
|
||||||
uniforms = &b.colUniforms.vert.blitUniforms
|
uniforms = &b.colUniforms.vert.blitUniforms
|
||||||
case materialTexture:
|
case materialTexture:
|
||||||
b.texUniforms.vert.uvTransform = [4]float32{uvScale.X, uvScale.Y, uvOff.X, uvOff.Y}
|
t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
|
||||||
|
b.texUniforms.vert.blitUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
|
||||||
|
b.texUniforms.vert.blitUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
|
||||||
uniforms = &b.texUniforms.vert.blitUniforms
|
uniforms = &b.texUniforms.vert.blitUniforms
|
||||||
}
|
}
|
||||||
uniforms.z = z
|
uniforms.z = z
|
||||||
@@ -994,6 +1027,7 @@ func clipSpaceTransform(r image.Rectangle, viewport image.Point) (f32.Point, f32
|
|||||||
// the rectangle at (x, y) and dimensions (w, h).
|
// the rectangle at (x, y) and dimensions (w, h).
|
||||||
scale := f32.Point{X: w * .5, Y: h * .5}
|
scale := f32.Point{X: w * .5, Y: h * .5}
|
||||||
offset := f32.Point{X: x + w*.5, Y: y - h*.5}
|
offset := f32.Point{X: x + w*.5, Y: y - h*.5}
|
||||||
|
|
||||||
return scale, offset
|
return scale, offset
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1043,9 +1077,8 @@ func (d *drawOps) writeVertCache(n int) []byte {
|
|||||||
return d.vertCache[len(d.vertCache)-n:]
|
return d.vertCache[len(d.vertCache)-n:]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *drawOps) buildVerts(aux []byte) (verts []byte, bounds f32.Rectangle) {
|
// transform, split paths as needed, calculate maxY, bounds and create GPU vertices.
|
||||||
// split paths as needed, calculate maxY, bounds and create
|
func (d *drawOps) buildVerts(aux []byte, tr f32.Affine2D) (verts []byte, bounds f32.Rectangle) {
|
||||||
// vertices that will be sent to GPU.
|
|
||||||
inf := float32(math.Inf(+1))
|
inf := float32(math.Inf(+1))
|
||||||
d.qs.bounds = f32.Rectangle{
|
d.qs.bounds = f32.Rectangle{
|
||||||
Min: f32.Point{X: inf, Y: inf},
|
Min: f32.Point{X: inf, Y: inf},
|
||||||
@@ -1057,6 +1090,7 @@ func (d *drawOps) buildVerts(aux []byte) (verts []byte, bounds f32.Rectangle) {
|
|||||||
for qi := 0; len(aux) >= (ops.QuadSize + 4); qi++ {
|
for qi := 0; len(aux) >= (ops.QuadSize + 4); qi++ {
|
||||||
d.qs.contour = bo.Uint32(aux)
|
d.qs.contour = bo.Uint32(aux)
|
||||||
quad := ops.DecodeQuad(aux[4:])
|
quad := ops.DecodeQuad(aux[4:])
|
||||||
|
quad = quad.Transform(tr)
|
||||||
|
|
||||||
d.qs.splitAndEncode(quad)
|
d.qs.splitAndEncode(quad)
|
||||||
|
|
||||||
@@ -1066,3 +1100,65 @@ func (d *drawOps) buildVerts(aux []byte) (verts []byte, bounds f32.Rectangle) {
|
|||||||
fillMaxY(d.vertCache[startLength:])
|
fillMaxY(d.vertCache[startLength:])
|
||||||
return d.vertCache[startLength:], d.qs.bounds
|
return d.vertCache[startLength:], d.qs.bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// create GPU vertices for transformed r, find the bounds and establish texture transform.
|
||||||
|
func (d *drawOps) boundsForTransformedRect(r f32.Rectangle, tr f32.Affine2D) (aux []byte, bnd f32.Rectangle, ptr f32.Affine2D) {
|
||||||
|
if isPureOffset(tr) {
|
||||||
|
// fast-path to allow blitting of pure rectangles
|
||||||
|
_, _, ox, _, _, oy := tr.Elems()
|
||||||
|
off := f32.Pt(ox, oy)
|
||||||
|
bnd.Min = r.Min.Add(off)
|
||||||
|
bnd.Max = r.Max.Add(off)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// transform all corners, find new bounds
|
||||||
|
corners := [4]f32.Point{
|
||||||
|
tr.Transform(r.Min), tr.Transform(f32.Pt(r.Max.X, r.Min.Y)),
|
||||||
|
tr.Transform(r.Max), tr.Transform(f32.Pt(r.Min.X, r.Max.Y)),
|
||||||
|
}
|
||||||
|
bnd.Min = f32.Pt(math.MaxFloat32, math.MaxFloat32)
|
||||||
|
bnd.Max = f32.Pt(-math.MaxFloat32, -math.MaxFloat32)
|
||||||
|
for _, c := range corners {
|
||||||
|
if c.X < bnd.Min.X {
|
||||||
|
bnd.Min.X = c.X
|
||||||
|
}
|
||||||
|
if c.Y < bnd.Min.Y {
|
||||||
|
bnd.Min.Y = c.Y
|
||||||
|
}
|
||||||
|
if c.X > bnd.Max.X {
|
||||||
|
bnd.Max.X = c.X
|
||||||
|
}
|
||||||
|
if c.Y > bnd.Max.Y {
|
||||||
|
bnd.Max.Y = c.Y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// build the GPU vertices
|
||||||
|
l := len(d.vertCache)
|
||||||
|
d.vertCache = append(d.vertCache, make([]byte, vertStride*4*4)...)
|
||||||
|
aux = d.vertCache[l:]
|
||||||
|
encodeQuadTo(aux, 0, corners[0], corners[0].Add(corners[1]).Mul(0.5), corners[1])
|
||||||
|
encodeQuadTo(aux[vertStride*4:], 0, corners[1], corners[1].Add(corners[2]).Mul(0.5), corners[2])
|
||||||
|
encodeQuadTo(aux[vertStride*4*2:], 0, corners[2], corners[2].Add(corners[3]).Mul(0.5), corners[3])
|
||||||
|
encodeQuadTo(aux[vertStride*4*3:], 0, corners[3], corners[3].Add(corners[0]).Mul(0.5), corners[0])
|
||||||
|
fillMaxY(aux)
|
||||||
|
|
||||||
|
// establish the transform mapping from bounds rectangle to transformed corners
|
||||||
|
var P1, P2, P3 f32.Point
|
||||||
|
P1.X = (corners[1].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X)
|
||||||
|
P1.Y = (corners[1].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y)
|
||||||
|
P2.X = (corners[2].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X)
|
||||||
|
P2.Y = (corners[2].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y)
|
||||||
|
P3.X = (corners[3].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X)
|
||||||
|
P3.Y = (corners[3].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y)
|
||||||
|
sx, sy := P2.X-P3.X, P2.Y-P3.Y
|
||||||
|
ptr = f32.NewAffine2D(sx, P2.X-P1.X, P1.X-sx, sy, P2.Y-P1.Y, P1.Y-sy).Invert()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPureOffset(t f32.Affine2D) bool {
|
||||||
|
a, b, _, d, e, _ := t.Elems()
|
||||||
|
return a == 1 && b == 0 && d == 0 && e == 1
|
||||||
|
}
|
||||||
|
|||||||
+12
-12
@@ -54,7 +54,8 @@ type coverColUniforms struct {
|
|||||||
type coverUniforms struct {
|
type coverUniforms struct {
|
||||||
transform [4]float32
|
transform [4]float32
|
||||||
uvCoverTransform [4]float32
|
uvCoverTransform [4]float32
|
||||||
uvTransform [4]float32
|
uvTransformR1 [4]float32
|
||||||
|
uvTransformR2 [4]float32
|
||||||
z float32
|
z float32
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,9 +78,7 @@ type stenciler struct {
|
|||||||
|
|
||||||
type stencilUniforms struct {
|
type stencilUniforms struct {
|
||||||
vert struct {
|
vert struct {
|
||||||
transform [4]float32
|
transform [4]float32
|
||||||
pathOffset [2]float32
|
|
||||||
_ [8]byte // Padding to multiple of 16.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,8 +306,8 @@ func (p *pather) begin(sizes []image.Point) {
|
|||||||
p.stenciler.begin(sizes)
|
p.stenciler.begin(sizes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pather) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data *pathData) {
|
func (p *pather) stencilPath(bounds image.Rectangle, uv image.Point, data *pathData) {
|
||||||
p.stenciler.stencilPath(bounds, offset, uv, data)
|
p.stenciler.stencilPath(bounds, uv, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stenciler) beginIntersect(sizes []image.Point) {
|
func (s *stenciler) beginIntersect(sizes []image.Point) {
|
||||||
@@ -337,14 +336,13 @@ func (s *stenciler) begin(sizes []image.Point) {
|
|||||||
s.ctx.BindIndexBuffer(s.indexBuf)
|
s.ctx.BindIndexBuffer(s.indexBuf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data *pathData) {
|
func (s *stenciler) stencilPath(bounds image.Rectangle, uv image.Point, data *pathData) {
|
||||||
s.ctx.Viewport(uv.X, uv.Y, bounds.Dx(), bounds.Dy())
|
s.ctx.Viewport(uv.X, uv.Y, bounds.Dx(), bounds.Dy())
|
||||||
// Transform UI coordinates to OpenGL coordinates.
|
// Transform UI coordinates to OpenGL coordinates.
|
||||||
texSize := f32.Point{X: float32(bounds.Dx()), Y: float32(bounds.Dy())}
|
texSize := f32.Point{X: float32(bounds.Dx()), Y: float32(bounds.Dy())}
|
||||||
scale := f32.Point{X: 2 / texSize.X, Y: 2 / texSize.Y}
|
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}
|
orig := f32.Point{X: -1 - float32(bounds.Min.X)*2/texSize.X, Y: -1 - float32(bounds.Min.Y)*2/texSize.Y}
|
||||||
s.prog.uniforms.vert.transform = [4]float32{scale.X, scale.Y, orig.X, orig.Y}
|
s.prog.uniforms.vert.transform = [4]float32{scale.X, scale.Y, orig.X, orig.Y}
|
||||||
s.prog.uniforms.vert.pathOffset = [2]float32{offset.X, offset.Y}
|
|
||||||
s.prog.prog.UploadUniforms()
|
s.prog.prog.UploadUniforms()
|
||||||
// Draw in batches that fit in uint16 indices.
|
// Draw in batches that fit in uint16 indices.
|
||||||
start := 0
|
start := 0
|
||||||
@@ -361,11 +359,11 @@ func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv ima
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pather) cover(z float32, mat materialType, col f32color.RGBA, scale, off, uvScale, uvOff, coverScale, coverOff f32.Point) {
|
func (p *pather) cover(z float32, mat materialType, col f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) {
|
||||||
p.coverer.cover(z, mat, col, scale, off, uvScale, uvOff, coverScale, coverOff)
|
p.coverer.cover(z, mat, col, scale, off, uvTrans, coverScale, coverOff)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *coverer) cover(z float32, mat materialType, col f32color.RGBA, scale, off, uvScale, uvOff, coverScale, coverOff f32.Point) {
|
func (c *coverer) cover(z float32, mat materialType, col f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) {
|
||||||
p := c.prog[mat]
|
p := c.prog[mat]
|
||||||
c.ctx.BindProgram(p.prog)
|
c.ctx.BindProgram(p.prog)
|
||||||
var uniforms *coverUniforms
|
var uniforms *coverUniforms
|
||||||
@@ -374,7 +372,9 @@ func (c *coverer) cover(z float32, mat materialType, col f32color.RGBA, scale, o
|
|||||||
c.colUniforms.frag.color = col
|
c.colUniforms.frag.color = col
|
||||||
uniforms = &c.colUniforms.vert.coverUniforms
|
uniforms = &c.colUniforms.vert.coverUniforms
|
||||||
case materialTexture:
|
case materialTexture:
|
||||||
c.texUniforms.vert.uvTransform = [4]float32{uvScale.X, uvScale.Y, uvOff.X, uvOff.Y}
|
t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
|
||||||
|
c.texUniforms.vert.uvTransformR1 = [4]float32{t1, t2, t3, 0}
|
||||||
|
c.texUniforms.vert.uvTransformR2 = [4]float32{t4, t5, t6, 0}
|
||||||
uniforms = &c.texUniforms.vert.coverUniforms
|
uniforms = &c.texUniforms.vert.coverUniforms
|
||||||
}
|
}
|
||||||
uniforms.z = z
|
uniforms.z = z
|
||||||
|
|||||||
+55
-43
File diff suppressed because one or more lines are too long
@@ -8,7 +8,8 @@ precision highp float;
|
|||||||
|
|
||||||
layout(binding = 0) uniform Block {
|
layout(binding = 0) uniform Block {
|
||||||
vec4 transform;
|
vec4 transform;
|
||||||
vec4 uvTransform;
|
vec4 uvTransformR1;
|
||||||
|
vec4 uvTransformR2;
|
||||||
float z;
|
float z;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -21,5 +22,5 @@ layout(location = 0) out vec2 vUV;
|
|||||||
void main() {
|
void main() {
|
||||||
vec2 p = pos*transform.xy + transform.zw;
|
vec2 p = pos*transform.xy + transform.zw;
|
||||||
gl_Position = toClipSpace(vec4(p, z, 1));
|
gl_Position = toClipSpace(vec4(p, z, 1));
|
||||||
vUV = uv*uvTransform.xy + uvTransform.zw;
|
vUV = transform3x2(m3x2(uvTransformR1.xyz, uvTransformR2.xyz), vec3(uv,1)).xy;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ precision highp float;
|
|||||||
layout(binding = 0) uniform Block {
|
layout(binding = 0) uniform Block {
|
||||||
vec4 transform;
|
vec4 transform;
|
||||||
vec4 uvCoverTransform;
|
vec4 uvCoverTransform;
|
||||||
vec4 uvTransform;
|
vec4 uvTransformR1;
|
||||||
|
vec4 uvTransformR2;
|
||||||
float z;
|
float z;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -22,7 +23,7 @@ layout(location = 1) out vec2 vUV;
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
gl_Position = toClipSpace(vec4(pos*transform.xy + transform.zw, z, 1));
|
gl_Position = toClipSpace(vec4(pos*transform.xy + transform.zw, z, 1));
|
||||||
vUV = uv*uvTransform.xy + uvTransform.zw;
|
vUV = transform3x2(m3x2(uvTransformR1.xyz, uvTransformR2.xyz), vec3(uv,1)).xy;
|
||||||
vec3 uv3 = transform3x2(fboTextureTransform, vec3(uv, 1.0));
|
vec3 uv3 = transform3x2(fboTextureTransform, vec3(uv, 1.0));
|
||||||
vCoverUV = (uv3*vec3(uvCoverTransform.xy, 1.0)+vec3(uvCoverTransform.zw, 0.0)).xy;
|
vCoverUV = (uv3*vec3(uvCoverTransform.xy, 1.0)+vec3(uvCoverTransform.zw, 0.0)).xy;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ precision highp float;
|
|||||||
|
|
||||||
layout(binding = 0) uniform Block {
|
layout(binding = 0) uniform Block {
|
||||||
vec4 transform;
|
vec4 transform;
|
||||||
vec2 pathOffset;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
layout(location=0) in float corner;
|
layout(location=0) in float corner;
|
||||||
@@ -23,10 +22,10 @@ void main() {
|
|||||||
// Add a one pixel overlap so curve quads cover their
|
// Add a one pixel overlap so curve quads cover their
|
||||||
// entire curves. Could use conservative rasterization
|
// entire curves. Could use conservative rasterization
|
||||||
// if available.
|
// if available.
|
||||||
vec2 from = from + pathOffset;
|
vec2 from = from;
|
||||||
vec2 ctrl = ctrl + pathOffset;
|
vec2 ctrl = ctrl;
|
||||||
vec2 to = to + pathOffset;
|
vec2 to = to;
|
||||||
float maxy = maxy + pathOffset.y;
|
float maxy = maxy;
|
||||||
vec2 pos;
|
vec2 pos;
|
||||||
float c = corner;
|
float c = corner;
|
||||||
if (c >= 0.375) {
|
if (c >= 0.375) {
|
||||||
|
|||||||
@@ -16,6 +16,13 @@ type Quad struct {
|
|||||||
From, Ctrl, To f32.Point
|
From, Ctrl, To f32.Point
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q Quad) Transform(t f32.Affine2D) Quad {
|
||||||
|
q.From = t.Transform(q.From)
|
||||||
|
q.Ctrl = t.Transform(q.Ctrl)
|
||||||
|
q.To = t.Transform(q.To)
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
func EncodeQuad(d []byte, q Quad) {
|
func EncodeQuad(d []byte, q Quad) {
|
||||||
bo := binary.LittleEndian
|
bo := binary.LittleEndian
|
||||||
bo.PutUint32(d[0:], math.Float32bits(q.From.X))
|
bo.PutUint32(d[0:], math.Float32bits(q.From.X))
|
||||||
|
|||||||
+18
-3
@@ -5,6 +5,7 @@ package ops
|
|||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
|
||||||
|
"gioui.org/f32"
|
||||||
"gioui.org/internal/opconst"
|
"gioui.org/internal/opconst"
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
)
|
)
|
||||||
@@ -26,9 +27,10 @@ type EncodedOp struct {
|
|||||||
|
|
||||||
// Key is a unique key for a given op.
|
// Key is a unique key for a given op.
|
||||||
type Key struct {
|
type Key struct {
|
||||||
ops *op.Ops
|
ops *op.Ops
|
||||||
pc int
|
pc int
|
||||||
version int
|
version int
|
||||||
|
sx, hx, sy, hy float32
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shadow of op.MacroOp.
|
// Shadow of op.MacroOp.
|
||||||
@@ -52,6 +54,10 @@ type opMacroDef struct {
|
|||||||
endpc pc
|
endpc pc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Reader) NewKey(pc int) Key {
|
||||||
|
return Key{ops: r.ops, pc: pc, version: r.ops.Version()}
|
||||||
|
}
|
||||||
|
|
||||||
// Reset start reading from the op list.
|
// Reset start reading from the op list.
|
||||||
func (r *Reader) Reset(ops *op.Ops) {
|
func (r *Reader) Reset(ops *op.Ops) {
|
||||||
r.stack = r.stack[:0]
|
r.stack = r.stack[:0]
|
||||||
@@ -59,6 +65,15 @@ func (r *Reader) Reset(ops *op.Ops) {
|
|||||||
r.ops = ops
|
r.ops = ops
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (k Key) SetTransform(t f32.Affine2D) Key {
|
||||||
|
sx, hx, sy, hy, _, _ := t.Elems()
|
||||||
|
k.sx = sx
|
||||||
|
k.hx = hx
|
||||||
|
k.sy = sy
|
||||||
|
k.hy = hy
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Reader) Decode() (EncodedOp, bool) {
|
func (r *Reader) Decode() (EncodedOp, bool) {
|
||||||
if r.ops == nil {
|
if r.ops == nil {
|
||||||
return EncodedOp{}, false
|
return EncodedOp{}, false
|
||||||
|
|||||||
@@ -117,10 +117,10 @@ type InvalidateOp struct {
|
|||||||
At time.Time
|
At time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransformOp applies a transform to the current transform.
|
// TransformOp applies a transform to the current transform. The zero value
|
||||||
|
// for TransformOp represents the identity transform.
|
||||||
type TransformOp struct {
|
type TransformOp struct {
|
||||||
// TODO: general transformations.
|
t f32.Affine2D
|
||||||
offset f32.Point
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// stack tracks the integer identities of StackOp and MacroOp
|
// stack tracks the integer identities of StackOp and MacroOp
|
||||||
@@ -264,39 +264,33 @@ func (r InvalidateOp) Add(o *Ops) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Offset the transformation.
|
// Offset creates a TransformOp with the offset o.
|
||||||
|
func Offset(o f32.Point) TransformOp {
|
||||||
|
return TransformOp{t: f32.Affine2D{}.Offset(o)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Affine creates a TransformOp representing the transformation a.
|
||||||
|
func Affine(a f32.Affine2D) TransformOp {
|
||||||
|
return TransformOp{t: a}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offset the transfomraiton.
|
||||||
func (t TransformOp) Offset(o f32.Point) TransformOp {
|
func (t TransformOp) Offset(o f32.Point) TransformOp {
|
||||||
return t.Multiply(TransformOp{o})
|
t.t = t.t.Offset(o)
|
||||||
}
|
return t
|
||||||
|
|
||||||
// Invert the transformation.
|
|
||||||
func (t TransformOp) Invert() TransformOp {
|
|
||||||
return TransformOp{offset: t.offset.Mul(-1)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transform a point.
|
|
||||||
func (t TransformOp) Transform(p f32.Point) f32.Point {
|
|
||||||
return p.Add(t.offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multiply by a transformation.
|
|
||||||
func (t TransformOp) Multiply(t2 TransformOp) TransformOp {
|
|
||||||
return TransformOp{
|
|
||||||
offset: t.offset.Add(t2.offset),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t TransformOp) Add(o *Ops) {
|
func (t TransformOp) Add(o *Ops) {
|
||||||
data := o.Write(opconst.TypeTransformLen)
|
data := o.Write(opconst.TypeTransformLen)
|
||||||
data[0] = byte(opconst.TypeTransform)
|
data[0] = byte(opconst.TypeTransform)
|
||||||
bo := binary.LittleEndian
|
bo := binary.LittleEndian
|
||||||
// write it out as an affine matrix although we only support offset yet
|
a, b, c, d, e, f := t.t.Elems()
|
||||||
bo.PutUint32(data[1:], math.Float32bits(1.0))
|
bo.PutUint32(data[1:], math.Float32bits(a))
|
||||||
bo.PutUint32(data[1+4*1:], math.Float32bits(0))
|
bo.PutUint32(data[1+4*1:], math.Float32bits(b))
|
||||||
bo.PutUint32(data[1+4*2:], math.Float32bits(t.offset.X))
|
bo.PutUint32(data[1+4*2:], math.Float32bits(c))
|
||||||
bo.PutUint32(data[1+4*3:], math.Float32bits(0))
|
bo.PutUint32(data[1+4*3:], math.Float32bits(d))
|
||||||
bo.PutUint32(data[1+4*4:], math.Float32bits(1))
|
bo.PutUint32(data[1+4*4:], math.Float32bits(e))
|
||||||
bo.PutUint32(data[1+4*5:], math.Float32bits(t.offset.Y))
|
bo.PutUint32(data[1+4*5:], math.Float32bits(f))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stack) push() stackID {
|
func (s *stack) push() stackID {
|
||||||
|
|||||||
Reference in New Issue
Block a user