shiny/driver/gldriver: implement Texture.Fill.

Change-Id: I651fc5a8770c6902d89cf01bf445f88afe2c0134
Reviewed-on: https://go-review.googlesource.com/37415
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/shiny/driver/gldriver/texture.go b/shiny/driver/gldriver/texture.go
index 40187b5..a691ed8 100644
--- a/shiny/driver/gldriver/texture.go
+++ b/shiny/driver/gldriver/texture.go
@@ -17,6 +17,7 @@
 type textureImpl struct {
 	w    *windowImpl
 	id   gl.Texture
+	fb   gl.Framebuffer
 	size image.Point
 }
 
@@ -27,6 +28,10 @@
 	t.w.glctxMu.Lock()
 	defer t.w.glctxMu.Unlock()
 
+	if t.fb.Value != 0 {
+		t.w.glctx.DeleteFramebuffer(t.fb)
+		t.fb = gl.Framebuffer{}
+	}
 	t.w.glctx.DeleteTexture(t.id)
 	t.id = gl.Texture{}
 }
@@ -71,10 +76,39 @@
 }
 
 func (t *textureImpl) 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)
+	mvp := calcMVP(
+		t.size.X, t.size.Y,
+		minX, minY,
+		maxX, minY,
+		minX, maxY,
+	)
+
+	glctx := t.w.glctx
+
 	t.w.glctxMu.Lock()
 	defer t.w.glctxMu.Unlock()
 
-	// TODO.
+	create := t.fb.Value == 0
+	if create {
+		t.fb = glctx.CreateFramebuffer()
+	}
+	glctx.BindFramebuffer(gl.FRAMEBUFFER, t.fb)
+	if create {
+		glctx.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, t.id, 0)
+	}
+
+	glctx.Viewport(0, 0, t.size.X, t.size.Y)
+	doFill(t.w.s, t.w.glctx, mvp, src, op)
+
+	// We can't restore the GL state (i.e. bind the back buffer, also known as
+	// gl.Framebuffer{Value: 0}) right away, since we don't necessarily know
+	// the right viewport size yet. It is valid to call textureImpl.Fill before
+	// we've gotten our first size.Event. We bind it lazily instead.
+	t.w.backBufferBound = false
 }
 
 var quadCoords = f32Bytes(binary.LittleEndian,
diff --git a/shiny/driver/gldriver/window.go b/shiny/driver/gldriver/window.go
index 53408a4..522fcca 100644
--- a/shiny/driver/gldriver/window.go
+++ b/shiny/driver/gldriver/window.go
@@ -53,7 +53,14 @@
 	glctxMu sync.Mutex
 	glctx   gl.Context
 	worker  gl.Worker
+	// backBufferBound is whether the default Framebuffer, with ID 0, also
+	// known as the back buffer or the window's Framebuffer, is bound and its
+	// viewport is known to equal the window size. It can become false when we
+	// bind to a texture's Framebuffer or when the window size changes.
+	backBufferBound bool
 
+	// szMu protects only sz. If you need to hold both glctxMu and szMu, the
+	// lock ordering is to lock glctxMu first (and unlock it last).
 	szMu sync.Mutex
 	sz   size.Event
 }
@@ -121,46 +128,64 @@
 	}
 }
 
+func (w *windowImpl) bindBackBuffer() {
+	w.szMu.Lock()
+	sz := w.sz
+	w.szMu.Unlock()
+
+	w.backBufferBound = true
+	w.glctx.BindFramebuffer(gl.FRAMEBUFFER, gl.Framebuffer{Value: 0})
+	w.glctx.Viewport(0, 0, sz.WidthPx, sz.HeightPx)
+}
+
 func (w *windowImpl) fill(mvp f64.Aff3, src color.Color, op draw.Op) {
 	w.glctxMu.Lock()
 	defer w.glctxMu.Unlock()
 
-	useOp(w.glctx, op)
-	if !w.glctx.IsProgram(w.s.fill.program) {
-		p, err := compileProgram(w.glctx, fillVertexSrc, fillFragmentSrc)
+	if !w.backBufferBound {
+		w.bindBackBuffer()
+	}
+
+	doFill(w.s, w.glctx, mvp, src, op)
+}
+
+func doFill(s *screenImpl, glctx gl.Context, mvp f64.Aff3, src color.Color, op draw.Op) {
+	useOp(glctx, op)
+	if !glctx.IsProgram(s.fill.program) {
+		p, err := compileProgram(glctx, fillVertexSrc, fillFragmentSrc)
 		if err != nil {
 			// TODO: initialize this somewhere else we can better handle the error.
 			panic(err.Error())
 		}
-		w.s.fill.program = p
-		w.s.fill.pos = w.glctx.GetAttribLocation(p, "pos")
-		w.s.fill.mvp = w.glctx.GetUniformLocation(p, "mvp")
-		w.s.fill.color = w.glctx.GetUniformLocation(p, "color")
-		w.s.fill.quad = w.glctx.CreateBuffer()
+		s.fill.program = p
+		s.fill.pos = glctx.GetAttribLocation(p, "pos")
+		s.fill.mvp = glctx.GetUniformLocation(p, "mvp")
+		s.fill.color = glctx.GetUniformLocation(p, "color")
+		s.fill.quad = glctx.CreateBuffer()
 
-		w.glctx.BindBuffer(gl.ARRAY_BUFFER, w.s.fill.quad)
-		w.glctx.BufferData(gl.ARRAY_BUFFER, quadCoords, gl.STATIC_DRAW)
+		glctx.BindBuffer(gl.ARRAY_BUFFER, s.fill.quad)
+		glctx.BufferData(gl.ARRAY_BUFFER, quadCoords, gl.STATIC_DRAW)
 	}
-	w.glctx.UseProgram(w.s.fill.program)
+	glctx.UseProgram(s.fill.program)
 
-	writeAff3(w.glctx, w.s.fill.mvp, mvp)
+	writeAff3(glctx, s.fill.mvp, mvp)
 
 	r, g, b, a := src.RGBA()
-	w.glctx.Uniform4f(
-		w.s.fill.color,
+	glctx.Uniform4f(
+		s.fill.color,
 		float32(r)/65535,
 		float32(g)/65535,
 		float32(b)/65535,
 		float32(a)/65535,
 	)
 
-	w.glctx.BindBuffer(gl.ARRAY_BUFFER, w.s.fill.quad)
-	w.glctx.EnableVertexAttribArray(w.s.fill.pos)
-	w.glctx.VertexAttribPointer(w.s.fill.pos, 2, gl.FLOAT, false, 0, 0)
+	glctx.BindBuffer(gl.ARRAY_BUFFER, s.fill.quad)
+	glctx.EnableVertexAttribArray(s.fill.pos)
+	glctx.VertexAttribPointer(s.fill.pos, 2, gl.FLOAT, false, 0, 0)
 
-	w.glctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
+	glctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
 
-	w.glctx.DisableVertexAttribArray(w.s.fill.pos)
+	glctx.DisableVertexAttribArray(s.fill.pos)
 }
 
 func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {
@@ -200,6 +225,10 @@
 	w.glctxMu.Lock()
 	defer w.glctxMu.Unlock()
 
+	if !w.backBufferBound {
+		w.bindBackBuffer()
+	}
+
 	useOp(w.glctx, op)
 	w.glctx.UseProgram(w.s.texture.program)
 
@@ -279,26 +308,30 @@
 	drawer.Scale(w, dr, src, sr, op, opts)
 }
 
-// mvp returns the Model View Projection matrix that maps the quadCoords unit
-// square, (0, 0) to (1, 1), to a quad QV, such that QV in vertex shader space
-// corresponds to the quad QP in pixel space, where QP is defined by three of
-// its four corners - the arguments to this function. The three corners are
-// nominally the top-left, top-right and bottom-left, but there is no
-// constraint that e.g. tlx < trx.
-//
-// In pixel space, the window ranges from (0, 0) to (sz.WidthPx, sz.HeightPx).
-// The Y-axis points downwards.
-//
-// In vertex shader space, the window ranges from (-1, +1) to (+1, -1), which
-// is a 2-unit by 2-unit square. The Y-axis points upwards.
 func (w *windowImpl) mvp(tlx, tly, trx, try, blx, bly float64) f64.Aff3 {
 	w.szMu.Lock()
 	sz := w.sz
 	w.szMu.Unlock()
 
+	return calcMVP(sz.WidthPx, sz.HeightPx, tlx, tly, trx, try, blx, bly)
+}
+
+// calcMVP returns the Model View Projection matrix that maps the quadCoords
+// unit square, (0, 0) to (1, 1), to a quad QV, such that QV in vertex shader
+// space corresponds to the quad QP in pixel space, where QP is defined by
+// three of its four corners - the arguments to this function. The three
+// corners are nominally the top-left, top-right and bottom-left, but there is
+// no constraint that e.g. tlx < trx.
+//
+// In pixel space, the window ranges from (0, 0) to (widthPx, heightPx). The
+// Y-axis points downwards.
+//
+// In vertex shader space, the window ranges from (-1, +1) to (+1, -1), which
+// is a 2-unit by 2-unit square. The Y-axis points upwards.
+func calcMVP(widthPx, heightPx int, tlx, tly, trx, try, blx, bly float64) f64.Aff3 {
 	// Convert from pixel coords to vertex shader coords.
-	invHalfWidth := +2 / float64(sz.WidthPx)
-	invHalfHeight := -2 / float64(sz.HeightPx)
+	invHalfWidth := +2 / float64(widthPx)
+	invHalfHeight := -2 / float64(heightPx)
 	tlx = tlx*invHalfWidth - 1
 	tly = tly*invHalfHeight + 1
 	trx = trx*invHalfWidth - 1
diff --git a/shiny/driver/gldriver/x11.go b/shiny/driver/gldriver/x11.go
index b32cbc7..28ef104 100644
--- a/shiny/driver/gldriver/x11.go
+++ b/shiny/driver/gldriver/x11.go
@@ -279,7 +279,10 @@
 	// logic'?
 	go func() {
 		w.glctxMu.Lock()
-		w.glctx.Viewport(0, 0, int(width), int(height))
+		// Force a w.glctx.Viewport call.
+		//
+		// TODO: is this racy? See also the TODO immediately above.
+		w.backBufferBound = false
 		w.glctxMu.Unlock()
 	}()
 
diff --git a/shiny/example/basic/main.go b/shiny/example/basic/main.go
index 42b42d1..739faf6 100644
--- a/shiny/example/basic/main.go
+++ b/shiny/example/basic/main.go
@@ -32,6 +32,7 @@
 	blue0    = color.RGBA{0x00, 0x00, 0x1f, 0xff}
 	blue1    = color.RGBA{0x00, 0x00, 0x3f, 0xff}
 	darkGray = color.RGBA{0x3f, 0x3f, 0x3f, 0xff}
+	green    = color.RGBA{0x00, 0x7f, 0x00, 0x7f}
 	red      = color.RGBA{0x7f, 0x00, 0x00, 0x7f}
 	yellow   = color.RGBA{0x3f, 0x3f, 0x00, 0x3f}
 
@@ -49,20 +50,30 @@
 		}
 		defer w.Release()
 
-		winSize := image.Point{256, 256}
-		b, err := s.NewBuffer(winSize)
+		size0 := image.Point{256, 256}
+		b, err := s.NewBuffer(size0)
 		if err != nil {
 			log.Fatal(err)
 		}
 		defer b.Release()
 		drawGradient(b.RGBA())
 
-		t, err := s.NewTexture(winSize)
+		t0, err := s.NewTexture(size0)
 		if err != nil {
 			log.Fatal(err)
 		}
-		defer t.Release()
-		t.Upload(image.Point{}, b, b.Bounds())
+		defer t0.Release()
+		t0.Upload(image.Point{}, b, b.Bounds())
+
+		size1 := image.Point{32, 20}
+		t1, err := s.NewTexture(size1)
+		if err != nil {
+			log.Fatal(err)
+		}
+		defer t1.Release()
+		t1.Fill(t1.Bounds(), green, screen.Src)
+		t1.Fill(t1.Bounds().Inset(2), red, screen.Over)
+		t1.Fill(t1.Bounds().Inset(4), red, screen.Src)
 
 		var sz size.Event
 		for {
@@ -102,22 +113,22 @@
 				// their different effects.
 				op := screen.Over
 				// op = screen.Src
-				tRect := t.Bounds()
-				// tRect = image.Rect(16, 0, 240, 100)
+				t0Rect := t0.Bounds()
+				// t0Rect = image.Rect(16, 0, 240, 100)
 
-				// Draw the texture t twice, as a 1:1 copy and under the
+				// Draw the texture t0 twice, as a 1:1 copy and under the
 				// transform src2dst.
-				w.Copy(image.Point{150, 100}, t, tRect, op, nil)
+				w.Copy(image.Point{150, 100}, t0, t0Rect, op, nil)
 				src2dst := f64.Aff3{
 					+0.5 * cos30, -1.0 * sin30, 100,
 					+0.5 * sin30, +1.0 * cos30, 200,
 				}
-				w.Draw(src2dst, t, tRect, op, nil)
-				w.DrawUniform(src2dst, yellow, tRect.Inset(30), screen.Over, nil)
+				w.Draw(src2dst, t0, t0Rect, op, nil)
+				w.DrawUniform(src2dst, yellow, t0Rect.Inset(30), screen.Over, nil)
 
-				// Draw crosses at the transformed corners of tRect.
-				for _, sx := range []int{tRect.Min.X, tRect.Max.X} {
-					for _, sy := range []int{tRect.Min.Y, tRect.Max.Y} {
+				// Draw crosses at the transformed corners of t0Rect.
+				for _, sx := range []int{t0Rect.Min.X, t0Rect.Max.X} {
+					for _, sy := range []int{t0Rect.Min.Y, t0Rect.Max.Y} {
 						dx := int(src2dst[0]*float64(sx) + src2dst[1]*float64(sy) + src2dst[2])
 						dy := int(src2dst[3]*float64(sx) + src2dst[4]*float64(sy) + src2dst[5])
 						w.Fill(image.Rect(dx-0, dy-1, dx+1, dy+2), darkGray, screen.Src)
@@ -125,6 +136,9 @@
 					}
 				}
 
+				// Draw t1.
+				w.Copy(image.Point{400, 50}, t1, t1.Bounds(), screen.Src, nil)
+
 				w.Publish()
 
 			case size.Event: