shiny/screen: add a DrawUniform method.
Change-Id: I4126fe22ac6262627a20332e5bac148c4981045c
Reviewed-on: https://go-review.googlesource.com/24754
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/shiny/driver/gldriver/window.go b/shiny/driver/gldriver/window.go
index 50d5f71..53408a4 100644
--- a/shiny/driver/gldriver/window.go
+++ b/shiny/driver/gldriver/window.go
@@ -121,7 +121,7 @@
}
}
-func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {
+func (w *windowImpl) fill(mvp f64.Aff3, src color.Color, op draw.Op) {
w.glctxMu.Lock()
defer w.glctxMu.Unlock()
@@ -143,15 +143,7 @@
}
w.glctx.UseProgram(w.s.fill.program)
- dstL := float64(dr.Min.X)
- dstT := float64(dr.Min.Y)
- dstR := float64(dr.Max.X)
- dstB := float64(dr.Max.Y)
- writeAff3(w.glctx, w.s.fill.mvp, w.mvp(
- dstL, dstT,
- dstR, dstT,
- dstL, dstB,
- ))
+ writeAff3(w.glctx, w.s.fill.mvp, mvp)
r, g, b, a := src.RGBA()
w.glctx.Uniform4f(
@@ -171,6 +163,33 @@
w.glctx.DisableVertexAttribArray(w.s.fill.pos)
}
+func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {
+ minX := float64(dr.Min.X)
+ minY := float64(dr.Min.Y)
+ maxX := float64(dr.Max.X)
+ maxY := float64(dr.Max.Y)
+ w.fill(w.mvp(
+ minX, minY,
+ maxX, minY,
+ minX, maxY,
+ ), src, op)
+}
+
+func (w *windowImpl) DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
+ minX := float64(sr.Min.X)
+ minY := float64(sr.Min.Y)
+ maxX := float64(sr.Max.X)
+ maxY := float64(sr.Max.Y)
+ w.fill(w.mvp(
+ src2dst[0]*minX+src2dst[1]*minY+src2dst[2],
+ src2dst[3]*minX+src2dst[4]*minY+src2dst[5],
+ src2dst[0]*maxX+src2dst[1]*minY+src2dst[2],
+ src2dst[3]*maxX+src2dst[4]*minY+src2dst[5],
+ src2dst[0]*minX+src2dst[1]*maxY+src2dst[2],
+ src2dst[3]*minX+src2dst[4]*maxY+src2dst[5],
+ ), src, op)
+}
+
func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
t := src.(*textureImpl)
sr = sr.Intersect(t.Bounds())
diff --git a/shiny/driver/windriver/window.go b/shiny/driver/windriver/window.go
index 3387bc7..9945974 100644
--- a/shiny/driver/windriver/window.go
+++ b/shiny/driver/windriver/window.go
@@ -77,6 +77,10 @@
})
}
+func (w *windowImpl) DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
+ panic("TODO: implement")
+}
+
func drawWindow(dc syscall.Handle, src2dst f64.Aff3, src syscall.Handle, sr image.Rectangle, op draw.Op) (retErr error) {
var dr image.Rectangle
if src2dst[1] != 0 || src2dst[3] != 0 {
diff --git a/shiny/driver/x11driver/screen.go b/shiny/driver/x11driver/screen.go
index dcbfa89..2a55051 100644
--- a/shiny/driver/x11driver/screen.go
+++ b/shiny/driver/x11driver/screen.go
@@ -7,6 +7,8 @@
import (
"fmt"
"image"
+ "image/color"
+ "image/draw"
"log"
"sync"
@@ -17,6 +19,7 @@
"golang.org/x/exp/shiny/driver/internal/x11key"
"golang.org/x/exp/shiny/screen"
+ "golang.org/x/image/math/f64"
"golang.org/x/mobile/event/key"
"golang.org/x/mobile/event/mouse"
)
@@ -44,6 +47,13 @@
gcontext32 xproto.Gcontext
window32 xproto.Window
+ // opaqueP is a fully opaque, solid fill picture.
+ opaqueP render.Picture
+
+ uniformMu sync.Mutex
+ uniformC render.Color
+ uniformP render.Picture
+
mu sync.Mutex
buffers map[shm.Seg]*bufferImpl
uploads map[uint16]chan struct{}
@@ -78,6 +88,24 @@
if err := s.initWindow32(); err != nil {
return nil, err
}
+
+ var err error
+ s.opaqueP, err = render.NewPictureId(xc)
+ if err != nil {
+ return nil, fmt.Errorf("x11driver: xproto.NewPictureId failed: %v", err)
+ }
+ s.uniformP, err = render.NewPictureId(xc)
+ if err != nil {
+ return nil, fmt.Errorf("x11driver: xproto.NewPictureId failed: %v", err)
+ }
+ render.CreateSolidFill(s.xc, s.opaqueP, render.Color{
+ Red: 0xffff,
+ Green: 0xffff,
+ Blue: 0xffff,
+ Alpha: 0xffff,
+ })
+ render.CreateSolidFill(s.xc, s.uniformP, render.Color{})
+
go s.run()
return s, nil
}
@@ -313,14 +341,6 @@
if err != nil {
return nil, fmt.Errorf("x11driver: xproto.NewPictureId failed: %v", err)
}
- opaqueM, err := xproto.NewPixmapId(s.xc)
- if err != nil {
- return nil, fmt.Errorf("x11driver: xproto.NewPixmapId failed: %v", err)
- }
- opaqueP, err := render.NewPictureId(s.xc)
- if err != nil {
- return nil, fmt.Errorf("x11driver: xproto.NewPictureId failed: %v", err)
- }
xproto.CreatePixmap(s.xc, textureDepth, xm, xproto.Drawable(s.window32), uint16(w), uint16(h))
render.CreatePicture(s.xc, xp, xproto.Drawable(xm), s.pictformat32, render.CpRepeat, []uint32{render.RepeatPad})
render.SetPictureFilter(s.xc, xp, uint16(len("bilinear")), "bilinear", nil)
@@ -331,12 +351,10 @@
}})
return &textureImpl{
- s: s,
- size: size,
- xm: xm,
- xp: xp,
- opaqueM: opaqueM,
- opaqueP: opaqueP,
+ s: s,
+ size: size,
+ xm: xm,
+ xp: xp,
}, nil
}
@@ -552,3 +570,39 @@
}
xproto.ChangeProperty(s.xc, xproto.PropModeReplace, xw, prop, xproto.AtomAtom, 32, uint32(len(values)), b)
}
+
+func (s *screenImpl) drawUniform(xp render.Picture, src2dst *f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
+ if sr.Empty() {
+ return
+ }
+
+ if opts == nil && *src2dst == (f64.Aff3{1, 0, 0, 0, 1, 0}) {
+ fill(s.xc, xp, sr, src, op)
+ return
+ }
+
+ r, g, b, a := src.RGBA()
+ c := render.Color{
+ Red: uint16(r),
+ Green: uint16(g),
+ Blue: uint16(b),
+ Alpha: uint16(a),
+ }
+ points := trifanPoints(src2dst, sr)
+
+ s.uniformMu.Lock()
+ defer s.uniformMu.Unlock()
+
+ if s.uniformC != c {
+ s.uniformC = c
+ render.FreePicture(s.xc, s.uniformP)
+ render.CreateSolidFill(s.xc, s.uniformP, c)
+ }
+
+ if op == draw.Src {
+ // We implement draw.Src as render.PictOpOutReverse followed by
+ // render.PictOpOver, for the same reason as in textureImpl.draw.
+ render.TriFan(s.xc, render.PictOpOutReverse, s.opaqueP, xp, 0, 0, 0, points[:])
+ }
+ render.TriFan(s.xc, render.PictOpOver, s.uniformP, xp, 0, 0, 0, points[:])
+}
diff --git a/shiny/driver/x11driver/texture.go b/shiny/driver/x11driver/texture.go
index 3ee3a91..da6fc86 100644
--- a/shiny/driver/x11driver/texture.go
+++ b/shiny/driver/x11driver/texture.go
@@ -32,13 +32,7 @@
// multiple X11/Render calls. X11/Render is a stateful API, so interleaving
// X11/Render calls from separate higher-level operations causes
// inconsistencies.
- //
- // It also protects the opaqueXxx fields, which hold a lazily created,
- // fully opaque mask picture.
- renderMu sync.Mutex
- opaqueM xproto.Pixmap
- opaqueP render.Picture
- opaqueCreated bool
+ renderMu sync.Mutex
releasedMu sync.Mutex
released bool
@@ -57,10 +51,6 @@
if released || t.degenerate() {
return
}
- if t.opaqueCreated {
- render.FreePicture(t.s.xc, t.opaqueP)
- xproto.FreePixmap(t.s.xc, t.opaqueM)
- }
render.FreePicture(t.s.xc, t.xp)
xproto.FreePixmap(t.s.xc, t.xm)
}
@@ -151,41 +141,8 @@
0, 0, 1 << 16,
})
- minX := float64(sr.Min.X)
- maxX := float64(sr.Max.X)
- minY := float64(sr.Min.Y)
- maxY := float64(sr.Max.Y)
- points := [4]render.Pointfix{{
- f64ToFixed(src2dst[0]*minX + src2dst[1]*minY + src2dst[2]),
- f64ToFixed(src2dst[3]*minX + src2dst[4]*minY + src2dst[5]),
- }, {
- f64ToFixed(src2dst[0]*maxX + src2dst[1]*minY + src2dst[2]),
- f64ToFixed(src2dst[3]*maxX + src2dst[4]*minY + src2dst[5]),
- }, {
- f64ToFixed(src2dst[0]*maxX + src2dst[1]*maxY + src2dst[2]),
- f64ToFixed(src2dst[3]*maxX + src2dst[4]*maxY + src2dst[5]),
- }, {
- f64ToFixed(src2dst[0]*minX + src2dst[1]*maxY + src2dst[2]),
- f64ToFixed(src2dst[3]*minX + src2dst[4]*maxY + src2dst[5]),
- }}
-
+ points := trifanPoints(src2dst, sr)
if op == draw.Src {
- // Lazily create the opaque mask picture.
- if !t.opaqueCreated {
- t.opaqueCreated = true
- xproto.CreatePixmap(t.s.xc, textureDepth, t.opaqueM, xproto.Drawable(t.s.window32), 1, 1)
- render.CreatePicture(t.s.xc, t.opaqueP, xproto.Drawable(t.opaqueM), t.s.pictformat32, 0, nil)
- render.FillRectangles(t.s.xc, render.PictOpSrc, t.opaqueP, render.Color{
- Red: 0xffff,
- Green: 0xffff,
- Blue: 0xffff,
- Alpha: 0xffff,
- }, []xproto.Rectangle{{
- Width: 1,
- Height: 1,
- }})
- }
-
// render.TriFan visits every dst-space pixel in the axis-aligned
// bounding box (AABB) containing the transformation of the sr
// rectangle in src-space to a quad in dst-space.
@@ -210,18 +167,31 @@
// What X11/Render calls PictOpOutReverse is also known as dst-out. See
// http://www.w3.org/TR/SVGCompositing/examples/compop-porterduff-examples.png
// for a visualization.
- invW := 1 / float64(sr.Dx())
- invH := 1 / float64(sr.Dy())
- render.SetPictureTransform(t.s.xc, t.opaqueP, render.Transform{
- f64ToFixed(invW * dst2src[0]), f64ToFixed(invW * dst2src[1]), 0,
- f64ToFixed(invH * dst2src[3]), f64ToFixed(invH * dst2src[4]), 0,
- 0, 0, 1 << 16,
- })
- render.TriFan(t.s.xc, render.PictOpOutReverse, t.opaqueP, xp, 0, 0, 0, points[:])
+ render.TriFan(t.s.xc, render.PictOpOutReverse, t.s.opaqueP, xp, 0, 0, 0, points[:])
}
render.TriFan(t.s.xc, render.PictOpOver, t.xp, xp, 0, 0, 0, points[:])
}
+func trifanPoints(src2dst *f64.Aff3, sr image.Rectangle) [4]render.Pointfix {
+ minX := float64(sr.Min.X)
+ maxX := float64(sr.Max.X)
+ minY := float64(sr.Min.Y)
+ maxY := float64(sr.Max.Y)
+ return [4]render.Pointfix{{
+ f64ToFixed(src2dst[0]*minX + src2dst[1]*minY + src2dst[2]),
+ f64ToFixed(src2dst[3]*minX + src2dst[4]*minY + src2dst[5]),
+ }, {
+ f64ToFixed(src2dst[0]*maxX + src2dst[1]*minY + src2dst[2]),
+ f64ToFixed(src2dst[3]*maxX + src2dst[4]*minY + src2dst[5]),
+ }, {
+ f64ToFixed(src2dst[0]*maxX + src2dst[1]*maxY + src2dst[2]),
+ f64ToFixed(src2dst[3]*maxX + src2dst[4]*maxY + src2dst[5]),
+ }, {
+ f64ToFixed(src2dst[0]*minX + src2dst[1]*maxY + src2dst[2]),
+ f64ToFixed(src2dst[3]*minX + src2dst[4]*maxY + src2dst[5]),
+ }}
+}
+
func renderOp(op draw.Op) byte {
if op == draw.Src {
return render.PictOpSrc
diff --git a/shiny/driver/x11driver/window.go b/shiny/driver/x11driver/window.go
index 3f5ca90..9234d0f 100644
--- a/shiny/driver/x11driver/window.go
+++ b/shiny/driver/x11driver/window.go
@@ -74,6 +74,10 @@
fill(w.s.xc, w.xp, dr, src, op)
}
+func (w *windowImpl) DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
+ w.s.drawUniform(w.xp, &src2dst, src, sr, op, opts)
+}
+
func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
src.(*textureImpl).draw(w.xp, &src2dst, sr, op, opts)
}
diff --git a/shiny/example/basic/main.go b/shiny/example/basic/main.go
index fe789fa..c3d7ee4 100644
--- a/shiny/example/basic/main.go
+++ b/shiny/example/basic/main.go
@@ -33,6 +33,7 @@
blue1 = color.RGBA{0x00, 0x00, 0x3f, 0xff}
darkGray = color.RGBA{0x3f, 0x3f, 0x3f, 0xff}
red = color.RGBA{0x7f, 0x00, 0x00, 0x7f}
+ yellow = color.RGBA{0x3f, 0x3f, 0x00, 0x3f}
cos30 = math.Cos(math.Pi / 6)
sin30 = math.Sin(math.Pi / 6)
@@ -110,6 +111,7 @@
+0.5 * sin30, +1.0 * cos30, 200,
}
w.Draw(src2dst, t, tRect, op, nil)
+ w.DrawUniform(src2dst, yellow, tRect.Inset(30), screen.Over, nil)
// Draw crosses at the transformed corners of tRect.
for _, sx := range []int{tRect.Min.X, tRect.Max.X} {
diff --git a/shiny/screen/screen.go b/shiny/screen/screen.go
index c0dd9a5..cbc1cb4 100644
--- a/shiny/screen/screen.go
+++ b/shiny/screen/screen.go
@@ -290,6 +290,10 @@
// (m00*sx + m01*sy + m02, m10*sx + m11*sy + m12).
Draw(src2dst f64.Aff3, src Texture, sr image.Rectangle, op draw.Op, opts *DrawOptions)
+ // DrawUniform is like Draw except that the src is a uniform color instead
+ // of a Texture.
+ DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op, opts *DrawOptions)
+
// Copy copies the sub-Texture defined by src and sr to the destination
// (the method receiver), such that sr.Min in src-space aligns with dp in
// dst-space.