shiny/driver/x11driver: fallback to regular pixmaps if SHM is unavaiable

This PR implement the **TODO** in buffer.go:
```
// TODO: detect if the X11 server or connection cannot support SHM pixmaps,
// and fall back to regular pixmaps.
```
It fixes the issues:
[golang/go#15100](https://github.com/golang/go/issues/15100)
[aarzilli/gdlv#26](https://github.com/aarzilli/gdlv/issues/26)
[aarzilli/gdlv#5](https://github.com/aarzilli/gdlv/issues/5)
[oakmound/shiny#8](https://github.com/oakmound/shiny/issues/8)

@sbinet
@aarzilli
@200sc

Change-Id: I7f157c95928ca5fde015935ea5fe1d1f8b03ea43
GitHub-Last-Rev: 0a5d514a6c5bc0ca7578a7339b99d1e794642d03
GitHub-Pull-Request: golang/exp#9
Reviewed-on: https://go-review.googlesource.com/c/exp/+/213199
Reviewed-by: Nigel Tao <nigeltao@golang.org>
diff --git a/shiny/driver/x11driver/buffer.go b/shiny/driver/x11driver/buffer.go
index c479639..a905928 100644
--- a/shiny/driver/x11driver/buffer.go
+++ b/shiny/driver/x11driver/buffer.go
@@ -16,10 +16,13 @@
 	"github.com/BurntSushi/xgb/render"
 	"github.com/BurntSushi/xgb/shm"
 	"github.com/BurntSushi/xgb/xproto"
-
 	"golang.org/x/exp/shiny/driver/internal/swizzle"
 )
 
+type bufferUploader interface {
+	upload(xd xproto.Drawable, xg xproto.Gcontext, depth uint8, dp image.Point, sr image.Rectangle)
+}
+
 type bufferImpl struct {
 	s *screenImpl
 
diff --git a/shiny/driver/x11driver/buffer_fallback.go b/shiny/driver/x11driver/buffer_fallback.go
new file mode 100644
index 0000000..bfdd9c7
--- /dev/null
+++ b/shiny/driver/x11driver/buffer_fallback.go
@@ -0,0 +1,123 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package x11driver
+
+import (
+	"image"
+	"sync"
+
+	"github.com/BurntSushi/xgb"
+	"github.com/BurntSushi/xgb/xproto"
+	"golang.org/x/exp/shiny/driver/internal/swizzle"
+)
+
+const (
+	xPutImageReqSizeMax   = (1 << 16) * 4
+	xPutImageReqSizeFixed = 28
+	xPutImageReqDataSize  = xPutImageReqSizeMax - xPutImageReqSizeFixed
+)
+
+type bufferFallbackImpl struct {
+	xc *xgb.Conn
+
+	buf  []byte
+	rgba image.RGBA
+	size image.Point
+
+	mu       sync.Mutex
+	nUpload  uint32
+	released bool
+}
+
+func (b *bufferFallbackImpl) Size() image.Point       { return b.size }
+func (b *bufferFallbackImpl) Bounds() image.Rectangle { return image.Rectangle{Max: b.size} }
+func (b *bufferFallbackImpl) RGBA() *image.RGBA       { return &b.rgba }
+
+func (b *bufferFallbackImpl) preUpload() {
+	// Check that the program hasn't tried to modify the rgba field via the
+	// pointer returned by the bufferFallbackImpl.RGBA method. This check doesn't catch
+	// 100% of all cases; it simply tries to detect some invalid uses of a
+	// screen.Buffer such as:
+	//	*buffer.RGBA() = anotherImageRGBA
+	if len(b.buf) != 0 && len(b.rgba.Pix) != 0 && &b.buf[0] != &b.rgba.Pix[0] {
+		panic("x11driver: invalid Buffer.RGBA modification")
+	}
+
+	b.mu.Lock()
+	defer b.mu.Unlock()
+
+	if b.released {
+		panic("x11driver: Buffer.Upload called after Buffer.Release")
+	}
+	if b.nUpload == 0 {
+		swizzle.BGRA(b.buf)
+	}
+	b.nUpload++
+}
+
+func (b *bufferFallbackImpl) postUpload() {
+	b.mu.Lock()
+	defer b.mu.Unlock()
+
+	b.nUpload--
+	if b.nUpload != 0 {
+		return
+	}
+
+	if !b.released {
+		swizzle.BGRA(b.buf)
+	}
+}
+
+func (b *bufferFallbackImpl) Release() {
+	b.mu.Lock()
+	defer b.mu.Unlock()
+
+	b.released = true
+}
+
+func (b *bufferFallbackImpl) upload(xd xproto.Drawable, xg xproto.Gcontext, depth uint8, dp image.Point, sr image.Rectangle) {
+	originalSRMin := sr.Min
+	sr = sr.Intersect(b.Bounds())
+	if sr.Empty() {
+		return
+	}
+	dp = dp.Add(sr.Min.Sub(originalSRMin))
+	b.preUpload()
+
+	b.putImage(xd, xg, depth, dp, sr)
+
+	b.postUpload()
+}
+
+// putImage issues xproto.PutImage requests in batches.
+func (b *bufferFallbackImpl) putImage(xd xproto.Drawable, xg xproto.Gcontext, depth uint8, dp image.Point, sr image.Rectangle) {
+	widthPerReq := b.size.X
+	rowPerReq := xPutImageReqDataSize / (widthPerReq * 4)
+	dataPerReq := rowPerReq * widthPerReq * 4
+	dstX := dp.X
+	dstY := dp.Y
+	start := 0
+	end := 0
+
+	for end < len(b.buf) {
+		end = start + dataPerReq
+		if end > len(b.buf) {
+			end = len(b.buf)
+		}
+
+		data := b.buf[start:end]
+		heightPerReq := len(data) / (widthPerReq * 4)
+
+		xproto.PutImage(
+			b.xc, xproto.ImageFormatZPixmap, xd, xg,
+			uint16(widthPerReq), uint16(heightPerReq),
+			int16(dstX), int16(dstY),
+			0, depth, data)
+
+		start = end
+		dstY += rowPerReq
+	}
+}
diff --git a/shiny/driver/x11driver/screen.go b/shiny/driver/x11driver/screen.go
index 7e0d3bb..0d0374b 100644
--- a/shiny/driver/x11driver/screen.go
+++ b/shiny/driver/x11driver/screen.go
@@ -51,6 +51,7 @@
 
 	// opaqueP is a fully opaque, solid fill picture.
 	opaqueP render.Picture
+	useShm  bool
 
 	uniformMu sync.Mutex
 	uniformC  render.Color
@@ -64,13 +65,14 @@
 	completionKeys  []uint16
 }
 
-func newScreenImpl(xc *xgb.Conn) (*screenImpl, error) {
+func newScreenImpl(xc *xgb.Conn, useShm bool) (*screenImpl, error) {
 	s := &screenImpl{
 		xc:      xc,
 		xsi:     xproto.Setup(xc).DefaultScreen(xc),
 		buffers: map[shm.Seg]*bufferImpl{},
 		uploads: map[uint16]chan struct{}{},
 		windows: map[xproto.Window]*windowImpl{},
+		useShm:  useShm,
 	}
 	if err := s.initAtoms(); err != nil {
 		return nil, err
@@ -282,14 +284,28 @@
 )
 
 func (s *screenImpl) NewBuffer(size image.Point) (retBuf screen.Buffer, retErr error) {
-	// TODO: detect if the X11 server or connection cannot support SHM pixmaps,
-	// and fall back to regular pixmaps.
 
 	w, h := int64(size.X), int64(size.Y)
 	if w < 0 || maxShmSide < w || h < 0 || maxShmSide < h || maxShmSize < 4*w*h {
 		return nil, fmt.Errorf("x11driver: invalid buffer size %v", size)
 	}
 
+	// If the X11 server or connection cannot support SHM pixmaps,
+	// fall back to regular pixmaps.
+	if !s.useShm {
+		b := &bufferFallbackImpl{
+			xc:   s.xc,
+			size: size,
+			rgba: image.RGBA{
+				Stride: 4 * size.X,
+				Rect:   image.Rectangle{Max: size},
+				Pix:    make([]uint8, 4*size.X*size.Y),
+			},
+		}
+		b.buf = b.rgba.Pix
+		return b, nil
+	}
+
 	b := &bufferImpl{
 		s: s,
 		rgba: image.RGBA{
diff --git a/shiny/driver/x11driver/texture.go b/shiny/driver/x11driver/texture.go
index da6fc86..8ca4759 100644
--- a/shiny/driver/x11driver/texture.go
+++ b/shiny/driver/x11driver/texture.go
@@ -59,7 +59,7 @@
 	if t.degenerate() {
 		return
 	}
-	src.(*bufferImpl).upload(xproto.Drawable(t.xm), t.s.gcontext32, textureDepth, dp, sr)
+	src.(bufferUploader).upload(xproto.Drawable(t.xm), t.s.gcontext32, textureDepth, dp, sr)
 }
 
 func (t *textureImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {
diff --git a/shiny/driver/x11driver/window.go b/shiny/driver/x11driver/window.go
index 5ebdbcd..f77b876 100644
--- a/shiny/driver/x11driver/window.go
+++ b/shiny/driver/x11driver/window.go
@@ -67,7 +67,7 @@
 }
 
 func (w *windowImpl) Upload(dp image.Point, src screen.Buffer, sr image.Rectangle) {
-	src.(*bufferImpl).upload(xproto.Drawable(w.xw), w.xg, w.s.xsi.RootDepth, dp, sr)
+	src.(bufferUploader).upload(xproto.Drawable(w.xw), w.xg, w.s.xsi.RootDepth, dp, sr)
 }
 
 func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {
diff --git a/shiny/driver/x11driver/x11driver.go b/shiny/driver/x11driver/x11driver.go
index 29d2b0d..1ba227e 100644
--- a/shiny/driver/x11driver/x11driver.go
+++ b/shiny/driver/x11driver/x11driver.go
@@ -47,11 +47,13 @@
 	if err := render.Init(xc); err != nil {
 		return fmt.Errorf("x11driver: render.Init failed: %v", err)
 	}
+
+	useShm := true
 	if err := shm.Init(xc); err != nil {
-		return fmt.Errorf("x11driver: shm.Init failed: %v", err)
+		useShm = false
 	}
 
-	s, err := newScreenImpl(xc)
+	s, err := newScreenImpl(xc, useShm)
 	if err != nil {
 		return err
 	}