| // Copyright 2015 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" |
| "image/color" |
| "image/draw" |
| "math" |
| "sync" |
| |
| "golang.org/x/exp/shiny/internal/xgb/render" |
| "golang.org/x/exp/shiny/internal/xgb/xproto" |
| |
| "golang.org/x/exp/shiny/screen" |
| "golang.org/x/image/math/f64" |
| ) |
| |
| const textureDepth = 32 |
| |
| type textureImpl struct { |
| s *screenImpl |
| |
| size image.Point |
| xm xproto.Pixmap |
| xp render.Picture |
| |
| // renderMu is a mutex that enforces the atomicity of methods like |
| // Window.Draw that are conceptually one operation but are implemented by |
| // multiple X11/Render calls. X11/Render is a stateful API, so interleaving |
| // X11/Render calls from separate higher-level operations causes |
| // inconsistencies. |
| renderMu sync.Mutex |
| |
| releasedMu sync.Mutex |
| released bool |
| } |
| |
| func (t *textureImpl) degenerate() bool { return t.size.X == 0 || t.size.Y == 0 } |
| func (t *textureImpl) Size() image.Point { return t.size } |
| func (t *textureImpl) Bounds() image.Rectangle { return image.Rectangle{Max: t.size} } |
| |
| func (t *textureImpl) Release() { |
| t.releasedMu.Lock() |
| released := t.released |
| t.released = true |
| t.releasedMu.Unlock() |
| |
| if released || t.degenerate() { |
| return |
| } |
| render.FreePicture(t.s.xc, t.xp) |
| xproto.FreePixmap(t.s.xc, t.xm) |
| } |
| |
| func (t *textureImpl) Upload(dp image.Point, src screen.Buffer, sr image.Rectangle) { |
| if t.degenerate() { |
| return |
| } |
| 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) { |
| if t.degenerate() { |
| return |
| } |
| fill(t.s.xc, t.xp, dr, src, op) |
| } |
| |
| // f64ToFixed converts from float64 to X11/Render's 16.16 fixed point. |
| func f64ToFixed(x float64) render.Fixed { |
| return render.Fixed(x * 65536) |
| } |
| |
| func inv(x *f64.Aff3) f64.Aff3 { |
| invDet := 1 / (x[0]*x[4] - x[1]*x[3]) |
| return f64.Aff3{ |
| +x[4] * invDet, |
| -x[1] * invDet, |
| (x[1]*x[5] - x[2]*x[4]) * invDet, |
| -x[3] * invDet, |
| +x[0] * invDet, |
| (x[2]*x[3] - x[0]*x[5]) * invDet, |
| } |
| } |
| |
| func (t *textureImpl) draw(xp render.Picture, src2dst *f64.Aff3, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) { |
| sr = sr.Intersect(t.Bounds()) |
| if sr.Empty() { |
| return |
| } |
| |
| t.renderMu.Lock() |
| defer t.renderMu.Unlock() |
| |
| // For simple copies and scales, the inverse matrix is trivial to compute, |
| // and we do not need the "Src becomes OutReverse plus Over" dance (see |
| // below). Thus, draw can be one render.SetPictureTransform call and then |
| // one render.Composite call, regardless of whether or not op is Src. |
| if src2dst[1] == 0 && src2dst[3] == 0 { |
| dstXMin := float64(sr.Min.X)*src2dst[0] + src2dst[2] |
| dstXMax := float64(sr.Max.X)*src2dst[0] + src2dst[2] |
| if dstXMin > dstXMax { |
| // TODO: check if this (and below) works when src2dst[0] < 0. |
| dstXMin, dstXMax = dstXMax, dstXMin |
| } |
| dXMin := int(math.Floor(dstXMin)) |
| dXMax := int(math.Ceil(dstXMax)) |
| |
| dstYMin := float64(sr.Min.Y)*src2dst[4] + src2dst[5] |
| dstYMax := float64(sr.Max.Y)*src2dst[4] + src2dst[5] |
| if dstYMin > dstYMax { |
| // TODO: check if this (and below) works when src2dst[4] < 0. |
| dstYMin, dstYMax = dstYMax, dstYMin |
| } |
| dYMin := int(math.Floor(dstYMin)) |
| dYMax := int(math.Ceil(dstYMax)) |
| |
| render.SetPictureTransform(t.s.xc, t.xp, render.Transform{ |
| f64ToFixed(1 / src2dst[0]), 0, 0, |
| 0, f64ToFixed(1 / src2dst[4]), 0, |
| 0, 0, 1 << 16, |
| }) |
| render.Composite(t.s.xc, renderOp(op), t.xp, 0, xp, |
| int16(sr.Min.X), int16(sr.Min.Y), // SrcX, SrcY, |
| 0, 0, // MaskX, MaskY, |
| int16(dXMin), int16(dYMin), // DstX, DstY, |
| uint16(dXMax-dXMin), uint16(dYMax-dYMin), // Width, Height, |
| ) |
| return |
| } |
| |
| // The X11/Render transform matrix maps from destination pixels to source |
| // pixels, so we invert src2dst. |
| dst2src := inv(src2dst) |
| render.SetPictureTransform(t.s.xc, t.xp, render.Transform{ |
| f64ToFixed(dst2src[0]), f64ToFixed(dst2src[1]), render.Fixed(sr.Min.X << 16), |
| f64ToFixed(dst2src[3]), f64ToFixed(dst2src[4]), render.Fixed(sr.Min.Y << 16), |
| 0, 0, 1 << 16, |
| }) |
| |
| points := trifanPoints(src2dst, sr) |
| if op == draw.Src { |
| // 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. |
| // |
| // render.TriFan is like render.Composite, except that the AABB is |
| // defined implicitly by the transformed triangle vertices instead of |
| // being passed explicitly as arguments. It implies the minimal AABB. |
| // |
| // In any case, for arbitrary src2dst affine transformations, which |
| // include rotations, this means that a naive render.TriFan call will |
| // affect those pixels inside the AABB but outside the quad. For the |
| // draw.Src operator, this means that pixels in that AABB can be |
| // incorrectly set to zero. |
| // |
| // Instead, we implement the draw.Src operator as two render.TriFan |
| // calls. The first one (using the PictOpOutReverse operator and a |
| // fully opaque source) clears the dst-space quad but leaves pixels |
| // outside that quad (but inside the AABB) untouched. The second one |
| // (using the PictOpOver operator and the texture t as source) fills in |
| // the quad and again does not touch the pixels outside. |
| // |
| // 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. |
| 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 |
| } |
| return render.PictOpOver |
| } |