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.