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
}