| // 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 |
| } |
| } |