mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
op/paint: add opacity operation
The new paint.PushOpacity allows for adjusting the opacity of a group of drawing operations. Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
@@ -5,7 +5,7 @@ go 1.19
|
|||||||
require (
|
require (
|
||||||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d
|
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d
|
||||||
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2
|
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2
|
||||||
gioui.org/shader v1.0.6
|
gioui.org/shader v1.0.7
|
||||||
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372
|
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372
|
||||||
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95
|
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95
|
||||||
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91
|
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8v
|
|||||||
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
||||||
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJGFDjINIPi1jtO6pc=
|
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJGFDjINIPi1jtO6pc=
|
||||||
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
||||||
gioui.org/shader v1.0.6 h1:cvZmU+eODFR2545X+/8XucgZdTtEjR3QWW6W65b0q5Y=
|
gioui.org/shader v1.0.7 h1:fDoor1Id/tRxoIpzBSAr5TBo6QfSkMTOmdbMEyWDgGE=
|
||||||
gioui.org/shader v1.0.6/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
|
gioui.org/shader v1.0.7/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
|
||||||
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372 h1:FQivqchis6bE2/9uF70M2gmmLpe82esEm2QadL0TEJo=
|
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372 h1:FQivqchis6bE2/9uF70M2gmmLpe82esEm2QadL0TEJo=
|
||||||
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k=
|
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k=
|
||||||
github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI=
|
github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI=
|
||||||
|
|||||||
+188
-42
@@ -68,22 +68,40 @@ type renderer struct {
|
|||||||
pather *pather
|
pather *pather
|
||||||
packer packer
|
packer packer
|
||||||
intersections packer
|
intersections packer
|
||||||
|
layers packer
|
||||||
|
layerFBOs fboSet
|
||||||
}
|
}
|
||||||
|
|
||||||
type drawOps struct {
|
type drawOps struct {
|
||||||
profile bool
|
profile bool
|
||||||
reader ops.Reader
|
reader ops.Reader
|
||||||
states []f32.Affine2D
|
states []f32.Affine2D
|
||||||
transStack []f32.Affine2D
|
transStack []f32.Affine2D
|
||||||
vertCache []byte
|
layers []opacityLayer
|
||||||
viewport image.Point
|
opacityStack []int
|
||||||
clear bool
|
vertCache []byte
|
||||||
clearColor f32color.RGBA
|
viewport image.Point
|
||||||
imageOps []imageOp
|
clear bool
|
||||||
pathOps []*pathOp
|
clearColor f32color.RGBA
|
||||||
pathOpCache []pathOp
|
imageOps []imageOp
|
||||||
qs quadSplitter
|
pathOps []*pathOp
|
||||||
pathCache *opCache
|
pathOpCache []pathOp
|
||||||
|
qs quadSplitter
|
||||||
|
pathCache *opCache
|
||||||
|
}
|
||||||
|
|
||||||
|
type opacityLayer struct {
|
||||||
|
opacity float32
|
||||||
|
parent int
|
||||||
|
// depth of the opacity stack. Layers of equal depth are
|
||||||
|
// independent and may be packed into one atlas.
|
||||||
|
depth int
|
||||||
|
// opStart and opEnd denote the range of drawOps.imageOps
|
||||||
|
// that belong to the layer.
|
||||||
|
opStart, opEnd int
|
||||||
|
// clip of the layer operations.
|
||||||
|
clip image.Rectangle
|
||||||
|
place placement
|
||||||
}
|
}
|
||||||
|
|
||||||
type drawState struct {
|
type drawState struct {
|
||||||
@@ -127,7 +145,12 @@ type imageOp struct {
|
|||||||
clip image.Rectangle
|
clip image.Rectangle
|
||||||
material material
|
material material
|
||||||
clipType clipType
|
clipType clipType
|
||||||
place placement
|
// place is either a placement in the path fbos or intersection fbos,
|
||||||
|
// depending on clipType.
|
||||||
|
place placement
|
||||||
|
// layerOps is the number of operations this
|
||||||
|
// operation replaces.
|
||||||
|
layerOps int
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeStrokeOp(data []byte) float32 {
|
func decodeStrokeOp(data []byte) float32 {
|
||||||
@@ -154,10 +177,12 @@ type material struct {
|
|||||||
// For materialTypeColor.
|
// For materialTypeColor.
|
||||||
color f32color.RGBA
|
color f32color.RGBA
|
||||||
// For materialTypeLinearGradient.
|
// For materialTypeLinearGradient.
|
||||||
color1 f32color.RGBA
|
color1 f32color.RGBA
|
||||||
color2 f32color.RGBA
|
color2 f32color.RGBA
|
||||||
|
opacity float32
|
||||||
// For materialTypeTexture.
|
// For materialTypeTexture.
|
||||||
data imageOpData
|
data imageOpData
|
||||||
|
tex driver.Texture
|
||||||
uvTrans f32.Affine2D
|
uvTrans f32.Affine2D
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,8 +247,6 @@ func decodeLinearGradientOp(data []byte) linearGradientOpData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type clipType uint8
|
|
||||||
|
|
||||||
type resource interface {
|
type resource interface {
|
||||||
release()
|
release()
|
||||||
}
|
}
|
||||||
@@ -273,6 +296,8 @@ type blitUniforms struct {
|
|||||||
transform [4]float32
|
transform [4]float32
|
||||||
uvTransformR1 [4]float32
|
uvTransformR1 [4]float32
|
||||||
uvTransformR2 [4]float32
|
uvTransformR2 [4]float32
|
||||||
|
opacity float32
|
||||||
|
_ [3]float32
|
||||||
}
|
}
|
||||||
|
|
||||||
type colorUniforms struct {
|
type colorUniforms struct {
|
||||||
@@ -284,7 +309,7 @@ type gradientUniforms struct {
|
|||||||
color2 f32color.RGBA
|
color2 f32color.RGBA
|
||||||
}
|
}
|
||||||
|
|
||||||
type materialType uint8
|
type clipType uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
clipTypeNone clipType = iota
|
clipTypeNone clipType = iota
|
||||||
@@ -292,6 +317,8 @@ const (
|
|||||||
clipTypeIntersection
|
clipTypeIntersection
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type materialType uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
materialColor materialType = iota
|
materialColor materialType = iota
|
||||||
materialLinearGradient
|
materialLinearGradient
|
||||||
@@ -391,6 +418,8 @@ func (g *gpu) frame(target RenderTarget) error {
|
|||||||
g.coverTimer.begin()
|
g.coverTimer.begin()
|
||||||
g.renderer.uploadImages(g.cache, g.drawOps.imageOps)
|
g.renderer.uploadImages(g.cache, g.drawOps.imageOps)
|
||||||
g.renderer.prepareDrawOps(g.cache, g.drawOps.imageOps)
|
g.renderer.prepareDrawOps(g.cache, g.drawOps.imageOps)
|
||||||
|
g.drawOps.layers = g.renderer.packLayers(g.drawOps.layers)
|
||||||
|
g.renderer.drawLayers(g.cache, g.drawOps.layers, g.drawOps.imageOps)
|
||||||
d := driver.LoadDesc{
|
d := driver.LoadDesc{
|
||||||
ClearColor: g.drawOps.clearColor,
|
ClearColor: g.drawOps.clearColor,
|
||||||
}
|
}
|
||||||
@@ -400,7 +429,7 @@ func (g *gpu) frame(target RenderTarget) error {
|
|||||||
}
|
}
|
||||||
g.ctx.BeginRenderPass(defFBO, d)
|
g.ctx.BeginRenderPass(defFBO, d)
|
||||||
g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
|
g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
|
||||||
g.renderer.drawOps(g.cache, g.drawOps.imageOps)
|
g.renderer.drawOps(g.cache, image.Point{}, g.renderer.blitter.viewport, g.drawOps.imageOps)
|
||||||
g.coverTimer.end()
|
g.coverTimer.end()
|
||||||
g.ctx.EndRenderPass()
|
g.ctx.EndRenderPass()
|
||||||
g.cleanupTimer.begin()
|
g.cleanupTimer.begin()
|
||||||
@@ -464,15 +493,18 @@ func newRenderer(ctx driver.Device) *renderer {
|
|||||||
if cap := 8192; maxDim > cap {
|
if cap := 8192; maxDim > cap {
|
||||||
maxDim = cap
|
maxDim = cap
|
||||||
}
|
}
|
||||||
|
d := image.Pt(maxDim, maxDim)
|
||||||
|
|
||||||
r.packer.maxDims = image.Pt(maxDim, maxDim)
|
r.packer.maxDims = d
|
||||||
r.intersections.maxDims = image.Pt(maxDim, maxDim)
|
r.intersections.maxDims = d
|
||||||
|
r.layers.maxDims = d
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *renderer) release() {
|
func (r *renderer) release() {
|
||||||
r.pather.release()
|
r.pather.release()
|
||||||
r.blitter.release()
|
r.blitter.release()
|
||||||
|
r.layerFBOs.delete(r.ctx, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBlitter(ctx driver.Device) *blitter {
|
func newBlitter(ctx driver.Device) *blitter {
|
||||||
@@ -747,8 +779,7 @@ func (r *renderer) packStencils(pops *[]*pathOp) {
|
|||||||
ops = ops[:len(ops)-1]
|
ops = ops[:len(ops)-1]
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sz := image.Point{X: p.clip.Dx(), Y: p.clip.Dy()}
|
place, ok := r.packer.add(p.clip.Size())
|
||||||
place, ok := r.packer.add(sz)
|
|
||||||
if !ok {
|
if !ok {
|
||||||
// The clip area is at most the entire screen. Hopefully no
|
// The clip area is at most the entire screen. Hopefully no
|
||||||
// screen is larger than GL_MAX_TEXTURE_SIZE.
|
// screen is larger than GL_MAX_TEXTURE_SIZE.
|
||||||
@@ -760,6 +791,83 @@ func (r *renderer) packStencils(pops *[]*pathOp) {
|
|||||||
*pops = ops
|
*pops = ops
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *renderer) packLayers(layers []opacityLayer) []opacityLayer {
|
||||||
|
// Make every layer bounds contain nested layers; cull empty layers.
|
||||||
|
for i := len(layers) - 1; i >= 0; i-- {
|
||||||
|
l := layers[i]
|
||||||
|
if l.parent != -1 {
|
||||||
|
b := layers[l.parent].clip
|
||||||
|
layers[l.parent].clip = b.Union(l.clip)
|
||||||
|
}
|
||||||
|
if l.clip.Empty() {
|
||||||
|
layers = append(layers[:i], layers[i+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Pack layers.
|
||||||
|
r.layers.clear()
|
||||||
|
depth := 0
|
||||||
|
for i := range layers {
|
||||||
|
l := &layers[i]
|
||||||
|
// Only layers of the same depth may be packed together.
|
||||||
|
if l.depth != depth {
|
||||||
|
r.layers.newPage()
|
||||||
|
}
|
||||||
|
place, ok := r.layers.add(l.clip.Size())
|
||||||
|
if !ok {
|
||||||
|
// The layer area is at most the entire screen. Hopefully no
|
||||||
|
// screen is larger than GL_MAX_TEXTURE_SIZE.
|
||||||
|
panic(fmt.Errorf("layer size %v is larger than maximum texture size %v", l.clip.Size(), r.layers.maxDims))
|
||||||
|
}
|
||||||
|
l.place = place
|
||||||
|
}
|
||||||
|
return layers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *renderer) drawLayers(cache *resourceCache, layers []opacityLayer, ops []imageOp) {
|
||||||
|
if len(r.layers.sizes) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fbo := -1
|
||||||
|
r.layerFBOs.resize(r.ctx, driver.TextureFormatSRGBA, r.layers.sizes)
|
||||||
|
for i := len(layers) - 1; i >= 0; i-- {
|
||||||
|
l := layers[i]
|
||||||
|
if fbo != l.place.Idx {
|
||||||
|
if fbo != -1 {
|
||||||
|
r.ctx.EndRenderPass()
|
||||||
|
r.ctx.PrepareTexture(r.layerFBOs.fbos[fbo].tex)
|
||||||
|
}
|
||||||
|
fbo = l.place.Idx
|
||||||
|
f := r.layerFBOs.fbos[fbo]
|
||||||
|
r.ctx.BeginRenderPass(f.tex, driver.LoadDesc{Action: driver.LoadActionClear})
|
||||||
|
}
|
||||||
|
v := image.Rectangle{
|
||||||
|
Min: l.place.Pos,
|
||||||
|
Max: l.place.Pos.Add(l.clip.Size()),
|
||||||
|
}
|
||||||
|
r.ctx.Viewport(v.Min.X, v.Min.Y, v.Max.X, v.Max.Y)
|
||||||
|
f := r.layerFBOs.fbos[fbo]
|
||||||
|
r.drawOps(cache, l.clip.Min.Mul(-1), l.clip.Size(), ops[l.opStart:l.opEnd])
|
||||||
|
sr := f32.FRect(v)
|
||||||
|
uvScale, uvOffset := texSpaceTransform(sr, f.size)
|
||||||
|
uvTrans := f32.Affine2D{}.Scale(f32.Point{}, uvScale).Offset(uvOffset)
|
||||||
|
// Replace layer ops with one textured op.
|
||||||
|
ops[l.opStart] = imageOp{
|
||||||
|
clip: l.clip,
|
||||||
|
material: material{
|
||||||
|
material: materialTexture,
|
||||||
|
tex: f.tex,
|
||||||
|
uvTrans: uvTrans,
|
||||||
|
opacity: l.opacity,
|
||||||
|
},
|
||||||
|
layerOps: l.opEnd - l.opStart - 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if fbo != -1 {
|
||||||
|
r.ctx.EndRenderPass()
|
||||||
|
r.ctx.PrepareTexture(r.layerFBOs.fbos[fbo].tex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (d *drawOps) reset(viewport image.Point) {
|
func (d *drawOps) reset(viewport image.Point) {
|
||||||
d.profile = false
|
d.profile = false
|
||||||
d.viewport = viewport
|
d.viewport = viewport
|
||||||
@@ -768,6 +876,8 @@ func (d *drawOps) reset(viewport image.Point) {
|
|||||||
d.pathOpCache = d.pathOpCache[:0]
|
d.pathOpCache = d.pathOpCache[:0]
|
||||||
d.vertCache = d.vertCache[:0]
|
d.vertCache = d.vertCache[:0]
|
||||||
d.transStack = d.transStack[:0]
|
d.transStack = d.transStack[:0]
|
||||||
|
d.layers = d.layers[:0]
|
||||||
|
d.opacityStack = d.opacityStack[:0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *drawOps) collect(root *op.Ops, viewport image.Point) {
|
func (d *drawOps) collect(root *op.Ops, viewport image.Point) {
|
||||||
@@ -866,6 +976,27 @@ loop:
|
|||||||
state.t = d.transStack[n-1]
|
state.t = d.transStack[n-1]
|
||||||
d.transStack = d.transStack[:n-1]
|
d.transStack = d.transStack[:n-1]
|
||||||
|
|
||||||
|
case ops.TypePushOpacity:
|
||||||
|
opacity := ops.DecodeOpacity(encOp.Data)
|
||||||
|
parent := -1
|
||||||
|
depth := len(d.opacityStack)
|
||||||
|
if depth > 0 {
|
||||||
|
parent = d.opacityStack[depth-1]
|
||||||
|
}
|
||||||
|
lidx := len(d.layers)
|
||||||
|
d.layers = append(d.layers, opacityLayer{
|
||||||
|
opacity: opacity,
|
||||||
|
parent: parent,
|
||||||
|
depth: depth,
|
||||||
|
opStart: len(d.imageOps),
|
||||||
|
})
|
||||||
|
d.opacityStack = append(d.opacityStack, lidx)
|
||||||
|
case ops.TypePopOpacity:
|
||||||
|
n := len(d.opacityStack)
|
||||||
|
idx := d.opacityStack[n-1]
|
||||||
|
d.layers[idx].opEnd = len(d.imageOps)
|
||||||
|
d.opacityStack = d.opacityStack[:n-1]
|
||||||
|
|
||||||
case ops.TypeStroke:
|
case ops.TypeStroke:
|
||||||
quads.key.strokeWidth = decodeStrokeOp(encOp.Data)
|
quads.key.strokeWidth = decodeStrokeOp(encOp.Data)
|
||||||
|
|
||||||
@@ -958,7 +1089,7 @@ loop:
|
|||||||
mat := state.materialFor(bnd, off, partialTrans, bounds)
|
mat := state.materialFor(bnd, off, partialTrans, bounds)
|
||||||
|
|
||||||
rect := state.cpath == nil || state.cpath.rect
|
rect := state.cpath == nil || state.cpath.rect
|
||||||
if bounds.Min == (image.Point{}) && bounds.Max == d.viewport && rect && mat.opaque && (mat.material == materialColor) {
|
if bounds.Min == (image.Point{}) && bounds.Max == d.viewport && rect && mat.opaque && (mat.material == materialColor) && len(d.opacityStack) == 0 {
|
||||||
// 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.
|
||||||
d.imageOps = d.imageOps[:0]
|
d.imageOps = d.imageOps[:0]
|
||||||
@@ -971,6 +1102,15 @@ loop:
|
|||||||
clip: bounds,
|
clip: bounds,
|
||||||
material: mat,
|
material: mat,
|
||||||
}
|
}
|
||||||
|
if n := len(d.opacityStack); n > 0 {
|
||||||
|
idx := d.opacityStack[n-1]
|
||||||
|
lb := d.layers[idx].clip
|
||||||
|
if lb.Empty() {
|
||||||
|
d.layers[idx].clip = img.clip
|
||||||
|
} else {
|
||||||
|
d.layers[idx].clip = lb.Union(img.clip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
d.imageOps = append(d.imageOps, img)
|
d.imageOps = append(d.imageOps, img)
|
||||||
if clipData != nil {
|
if clipData != nil {
|
||||||
@@ -1000,7 +1140,9 @@ func expandPathOp(p *pathOp, clip image.Rectangle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *drawState) materialFor(rect f32.Rectangle, off f32.Point, partTrans f32.Affine2D, clip image.Rectangle) material {
|
func (d *drawState) materialFor(rect f32.Rectangle, off f32.Point, partTrans f32.Affine2D, clip image.Rectangle) material {
|
||||||
var m material
|
m := material{
|
||||||
|
opacity: 1.,
|
||||||
|
}
|
||||||
switch d.matType {
|
switch d.matType {
|
||||||
case materialColor:
|
case materialColor:
|
||||||
m.material = materialColor
|
m.material = materialColor
|
||||||
@@ -1040,10 +1182,11 @@ func (d *drawState) materialFor(rect f32.Rectangle, off f32.Point, partTrans f32
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *renderer) uploadImages(cache *resourceCache, ops []imageOp) {
|
func (r *renderer) uploadImages(cache *resourceCache, ops []imageOp) {
|
||||||
for _, img := range ops {
|
for i := range ops {
|
||||||
|
img := &ops[i]
|
||||||
m := img.material
|
m := img.material
|
||||||
if m.material == materialTexture {
|
if m.material == materialTexture {
|
||||||
r.texHandle(cache, m.data)
|
img.material.tex = r.texHandle(cache, m.data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1053,10 +1196,10 @@ func (r *renderer) prepareDrawOps(cache *resourceCache, ops []imageOp) {
|
|||||||
m := img.material
|
m := img.material
|
||||||
switch m.material {
|
switch m.material {
|
||||||
case materialTexture:
|
case materialTexture:
|
||||||
r.ctx.PrepareTexture(r.texHandle(cache, m.data))
|
r.ctx.PrepareTexture(m.tex)
|
||||||
}
|
}
|
||||||
|
|
||||||
var fbo stencilFBO
|
var fbo FBO
|
||||||
switch img.clipType {
|
switch img.clipType {
|
||||||
case clipTypeNone:
|
case clipTypeNone:
|
||||||
continue
|
continue
|
||||||
@@ -1069,24 +1212,26 @@ func (r *renderer) prepareDrawOps(cache *resourceCache, ops []imageOp) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *renderer) drawOps(cache *resourceCache, ops []imageOp) {
|
func (r *renderer) drawOps(cache *resourceCache, opOff image.Point, viewport image.Point, ops []imageOp) {
|
||||||
var coverTex driver.Texture
|
var coverTex driver.Texture
|
||||||
for _, img := range ops {
|
for i := 0; i < len(ops); i++ {
|
||||||
|
img := ops[i]
|
||||||
|
i += img.layerOps
|
||||||
m := img.material
|
m := img.material
|
||||||
switch m.material {
|
switch m.material {
|
||||||
case materialTexture:
|
case materialTexture:
|
||||||
r.ctx.BindTexture(0, r.texHandle(cache, m.data))
|
r.ctx.BindTexture(0, m.tex)
|
||||||
}
|
}
|
||||||
drc := img.clip
|
drc := img.clip.Add(opOff)
|
||||||
|
|
||||||
scale, off := clipSpaceTransform(drc, r.blitter.viewport)
|
scale, off := clipSpaceTransform(drc, viewport)
|
||||||
var fbo stencilFBO
|
var fbo FBO
|
||||||
switch img.clipType {
|
switch img.clipType {
|
||||||
case clipTypeNone:
|
case clipTypeNone:
|
||||||
p := r.blitter.pipelines[m.material]
|
p := r.blitter.pipelines[m.material]
|
||||||
r.ctx.BindPipeline(p.pipeline)
|
r.ctx.BindPipeline(p.pipeline)
|
||||||
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
|
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
|
||||||
r.blitter.blit(m.material, m.color, m.color1, m.color2, scale, off, m.uvTrans)
|
r.blitter.blit(m.material, m.color, m.color1, m.color2, scale, off, m.opacity, m.uvTrans)
|
||||||
continue
|
continue
|
||||||
case clipTypePath:
|
case clipTypePath:
|
||||||
fbo = r.pather.stenciler.cover(img.place.Idx)
|
fbo = r.pather.stenciler.cover(img.place.Idx)
|
||||||
@@ -1109,7 +1254,7 @@ func (r *renderer) drawOps(cache *resourceCache, ops []imageOp) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *blitter) blit(mat materialType, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D) {
|
func (b *blitter) blit(mat materialType, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, opacity float32, uvTrans f32.Affine2D) {
|
||||||
p := b.pipelines[mat]
|
p := b.pipelines[mat]
|
||||||
b.ctx.BindPipeline(p.pipeline)
|
b.ctx.BindPipeline(p.pipeline)
|
||||||
var uniforms *blitUniforms
|
var uniforms *blitUniforms
|
||||||
@@ -1119,18 +1264,19 @@ func (b *blitter) blit(mat materialType, col f32color.RGBA, col1, col2 f32color.
|
|||||||
uniforms = &b.colUniforms.blitUniforms
|
uniforms = &b.colUniforms.blitUniforms
|
||||||
case materialTexture:
|
case materialTexture:
|
||||||
t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
|
t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
|
||||||
b.texUniforms.blitUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
|
|
||||||
b.texUniforms.blitUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
|
|
||||||
uniforms = &b.texUniforms.blitUniforms
|
uniforms = &b.texUniforms.blitUniforms
|
||||||
|
uniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
|
||||||
|
uniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
|
||||||
case materialLinearGradient:
|
case materialLinearGradient:
|
||||||
b.linearGradientUniforms.color1 = col1
|
b.linearGradientUniforms.color1 = col1
|
||||||
b.linearGradientUniforms.color2 = col2
|
b.linearGradientUniforms.color2 = col2
|
||||||
|
|
||||||
t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
|
t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
|
||||||
b.linearGradientUniforms.blitUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
|
|
||||||
b.linearGradientUniforms.blitUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
|
|
||||||
uniforms = &b.linearGradientUniforms.blitUniforms
|
uniforms = &b.linearGradientUniforms.blitUniforms
|
||||||
|
uniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
|
||||||
|
uniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
|
||||||
}
|
}
|
||||||
|
uniforms.opacity = opacity
|
||||||
uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
|
uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
|
||||||
p.UploadUniforms(b.ctx)
|
p.UploadUniforms(b.ctx)
|
||||||
b.ctx.DrawArrays(0, 4)
|
b.ctx.DrawArrays(0, 4)
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 334 B |
@@ -413,6 +413,22 @@ func TestGapsInPath(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOpacity(t *testing.T) {
|
||||||
|
run(t, func(ops *op.Ops) {
|
||||||
|
opc1 := paint.PushOpacity(ops, .3)
|
||||||
|
// Fill screen to exercize the glClear optimization.
|
||||||
|
paint.FillShape(ops, color.NRGBA{R: 255, A: 255}, clip.Rect{Max: image.Pt(1024, 1024)}.Op())
|
||||||
|
opc2 := paint.PushOpacity(ops, .6)
|
||||||
|
paint.FillShape(ops, color.NRGBA{G: 255, A: 255}, clip.Rect{Min: image.Pt(20, 10), Max: image.Pt(64, 128)}.Op())
|
||||||
|
opc2.Pop()
|
||||||
|
opc1.Pop()
|
||||||
|
opc3 := paint.PushOpacity(ops, .6)
|
||||||
|
paint.FillShape(ops, color.NRGBA{G: 255, A: 255}, clip.Rect{Min: image.Pt(50+20, 10), Max: image.Pt(50+64, 128)}.Op())
|
||||||
|
opc3.Pop()
|
||||||
|
}, func(r result) {
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// lerp calculates linear interpolation with color b and p.
|
// lerp calculates linear interpolation with color b and p.
|
||||||
func lerp(a, b f32color.RGBA, p float32) f32color.RGBA {
|
func lerp(a, b f32color.RGBA, p float32) f32color.RGBA {
|
||||||
return f32color.RGBA{
|
return f32color.RGBA{
|
||||||
|
|||||||
+8
-8
@@ -90,10 +90,10 @@ type intersectUniforms struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type fboSet struct {
|
type fboSet struct {
|
||||||
fbos []stencilFBO
|
fbos []FBO
|
||||||
}
|
}
|
||||||
|
|
||||||
type stencilFBO struct {
|
type FBO struct {
|
||||||
size image.Point
|
size image.Point
|
||||||
tex driver.Texture
|
tex driver.Texture
|
||||||
}
|
}
|
||||||
@@ -247,10 +247,10 @@ func newStenciler(ctx driver.Device) *stenciler {
|
|||||||
return st
|
return st
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *fboSet) resize(ctx driver.Device, sizes []image.Point) {
|
func (s *fboSet) resize(ctx driver.Device, format driver.TextureFormat, sizes []image.Point) {
|
||||||
// Add fbos.
|
// Add fbos.
|
||||||
for i := len(s.fbos); i < len(sizes); i++ {
|
for i := len(s.fbos); i < len(sizes); i++ {
|
||||||
s.fbos = append(s.fbos, stencilFBO{})
|
s.fbos = append(s.fbos, FBO{})
|
||||||
}
|
}
|
||||||
// Resize fbos.
|
// Resize fbos.
|
||||||
for i, sz := range sizes {
|
for i, sz := range sizes {
|
||||||
@@ -273,7 +273,7 @@ func (s *fboSet) resize(ctx driver.Device, sizes []image.Point) {
|
|||||||
if sz.X > max {
|
if sz.X > max {
|
||||||
sz.X = max
|
sz.X = max
|
||||||
}
|
}
|
||||||
tex, err := ctx.NewTexture(driver.TextureFormatFloat, sz.X, sz.Y, driver.FilterNearest, driver.FilterNearest,
|
tex, err := ctx.NewTexture(format, sz.X, sz.Y, driver.FilterNearest, driver.FilterNearest,
|
||||||
driver.BufferBindingTexture|driver.BufferBindingFramebuffer)
|
driver.BufferBindingTexture|driver.BufferBindingFramebuffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -340,15 +340,15 @@ func (s *stenciler) beginIntersect(sizes []image.Point) {
|
|||||||
// 8 bit coverage is enough, but OpenGL ES only supports single channel
|
// 8 bit coverage is enough, but OpenGL ES only supports single channel
|
||||||
// floating point formats. Replace with GL_RGB+GL_UNSIGNED_BYTE if
|
// floating point formats. Replace with GL_RGB+GL_UNSIGNED_BYTE if
|
||||||
// no floating point support is available.
|
// no floating point support is available.
|
||||||
s.intersections.resize(s.ctx, sizes)
|
s.intersections.resize(s.ctx, driver.TextureFormatFloat, sizes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stenciler) cover(idx int) stencilFBO {
|
func (s *stenciler) cover(idx int) FBO {
|
||||||
return s.fbos.fbos[idx]
|
return s.fbos.fbos[idx]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stenciler) begin(sizes []image.Point) {
|
func (s *stenciler) begin(sizes []image.Point) {
|
||||||
s.fbos.resize(s.ctx, sizes)
|
s.fbos.resize(s.ctx, driver.TextureFormatFloat, sizes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data pathData) {
|
func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data pathData) {
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ const (
|
|||||||
TypeDefer
|
TypeDefer
|
||||||
TypeTransform
|
TypeTransform
|
||||||
TypePopTransform
|
TypePopTransform
|
||||||
|
TypePushOpacity
|
||||||
|
TypePopOpacity
|
||||||
TypeInvalidate
|
TypeInvalidate
|
||||||
TypeImage
|
TypeImage
|
||||||
TypePaint
|
TypePaint
|
||||||
@@ -121,6 +123,7 @@ const (
|
|||||||
ClipStack StackKind = iota
|
ClipStack StackKind = iota
|
||||||
TransStack
|
TransStack
|
||||||
PassStack
|
PassStack
|
||||||
|
OpacityStack
|
||||||
_StackKind
|
_StackKind
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -136,6 +139,8 @@ const (
|
|||||||
TypeDeferLen = 1
|
TypeDeferLen = 1
|
||||||
TypeTransformLen = 1 + 1 + 4*6
|
TypeTransformLen = 1 + 1 + 4*6
|
||||||
TypePopTransformLen = 1
|
TypePopTransformLen = 1
|
||||||
|
TypePushOpacityLen = 1 + 4
|
||||||
|
TypePopOpacityLen = 1
|
||||||
TypeRedrawLen = 1 + 8
|
TypeRedrawLen = 1 + 8
|
||||||
TypeImageLen = 1
|
TypeImageLen = 1
|
||||||
TypePaintLen = 1
|
TypePaintLen = 1
|
||||||
@@ -381,6 +386,14 @@ func DecodeTransform(data []byte) (t f32.Affine2D, push bool) {
|
|||||||
return f32.NewAffine2D(a, b, c, d, e, f), push
|
return f32.NewAffine2D(a, b, c, d, e, f), push
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DecodeOpacity(data []byte) float32 {
|
||||||
|
if OpType(data[0]) != TypePushOpacity {
|
||||||
|
panic("invalid op")
|
||||||
|
}
|
||||||
|
bo := binary.LittleEndian
|
||||||
|
return math.Float32frombits(bo.Uint32(data[1:]))
|
||||||
|
}
|
||||||
|
|
||||||
// DecodeSave decodes the state id of a save op.
|
// DecodeSave decodes the state id of a save op.
|
||||||
func DecodeSave(data []byte) int {
|
func DecodeSave(data []byte) int {
|
||||||
if OpType(data[0]) != TypeSave {
|
if OpType(data[0]) != TypeSave {
|
||||||
@@ -410,6 +423,8 @@ var opProps = [0x100]opProp{
|
|||||||
TypeDefer: {Size: TypeDeferLen, NumRefs: 0},
|
TypeDefer: {Size: TypeDeferLen, NumRefs: 0},
|
||||||
TypeTransform: {Size: TypeTransformLen, NumRefs: 0},
|
TypeTransform: {Size: TypeTransformLen, NumRefs: 0},
|
||||||
TypePopTransform: {Size: TypePopTransformLen, NumRefs: 0},
|
TypePopTransform: {Size: TypePopTransformLen, NumRefs: 0},
|
||||||
|
TypePushOpacity: {Size: TypePushOpacityLen, NumRefs: 0},
|
||||||
|
TypePopOpacity: {Size: TypePopOpacityLen, NumRefs: 0},
|
||||||
TypeInvalidate: {Size: TypeRedrawLen, NumRefs: 0},
|
TypeInvalidate: {Size: TypeRedrawLen, NumRefs: 0},
|
||||||
TypeImage: {Size: TypeImageLen, NumRefs: 2},
|
TypeImage: {Size: TypeImageLen, NumRefs: 2},
|
||||||
TypePaint: {Size: TypePaintLen, NumRefs: 0},
|
TypePaint: {Size: TypePaintLen, NumRefs: 0},
|
||||||
@@ -470,6 +485,10 @@ func (t OpType) String() string {
|
|||||||
return "Transform"
|
return "Transform"
|
||||||
case TypePopTransform:
|
case TypePopTransform:
|
||||||
return "PopTransform"
|
return "PopTransform"
|
||||||
|
case TypePushOpacity:
|
||||||
|
return "PushOpacity"
|
||||||
|
case TypePopOpacity:
|
||||||
|
return "PopOpacity"
|
||||||
case TypeInvalidate:
|
case TypeInvalidate:
|
||||||
return "Invalidate"
|
return "Invalidate"
|
||||||
case TypeImage:
|
case TypeImage:
|
||||||
|
|||||||
@@ -44,6 +44,14 @@ type LinearGradientOp struct {
|
|||||||
type PaintOp struct {
|
type PaintOp struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpacityStack represents an opacity applied to all painting operations
|
||||||
|
// until Pop is called.
|
||||||
|
type OpacityStack struct {
|
||||||
|
id ops.StackID
|
||||||
|
macroID int
|
||||||
|
ops *ops.Ops
|
||||||
|
}
|
||||||
|
|
||||||
// NewImageOp creates an ImageOp backed by src.
|
// NewImageOp creates an ImageOp backed by src.
|
||||||
//
|
//
|
||||||
// NewImageOp assumes the backing image is immutable, and may cache a
|
// NewImageOp assumes the backing image is immutable, and may cache a
|
||||||
@@ -145,3 +153,31 @@ func Fill(ops *op.Ops, c color.NRGBA) {
|
|||||||
ColorOp{Color: c}.Add(ops)
|
ColorOp{Color: c}.Add(ops)
|
||||||
PaintOp{}.Add(ops)
|
PaintOp{}.Add(ops)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PushOpacity creates a drawing layer with an opacity in the range [0;1].
|
||||||
|
// The layer includes every subsequent drawing operation until [OpacityStack.Pop]
|
||||||
|
// is called.
|
||||||
|
//
|
||||||
|
// The layer is drawn in two steps. First, the layer operations are
|
||||||
|
// drawn to a separate image. Then, the image is blended on top of
|
||||||
|
// the frame, with the opacity used as the blending factor.
|
||||||
|
func PushOpacity(o *op.Ops, opacity float32) OpacityStack {
|
||||||
|
if opacity > 1 {
|
||||||
|
opacity = 1
|
||||||
|
}
|
||||||
|
if opacity < 0 {
|
||||||
|
opacity = 0
|
||||||
|
}
|
||||||
|
id, macroID := ops.PushOp(&o.Internal, ops.OpacityStack)
|
||||||
|
data := ops.Write(&o.Internal, ops.TypePushOpacityLen)
|
||||||
|
bo := binary.LittleEndian
|
||||||
|
data[0] = byte(ops.TypePushOpacity)
|
||||||
|
bo.PutUint32(data[1:], math.Float32bits(opacity))
|
||||||
|
return OpacityStack{ops: &o.Internal, id: id, macroID: macroID}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t OpacityStack) Pop() {
|
||||||
|
ops.PopOp(t.ops, ops.OpacityStack, t.id, t.macroID)
|
||||||
|
data := ops.Write(t.ops, ops.TypePopOpacityLen)
|
||||||
|
data[0] = byte(ops.TypePopOpacity)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user