github.com/as/shiny@v0.8.2/driver/x11driver/texture.go (about) 1 // Copyright 2015 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package x11driver 6 7 import ( 8 "image" 9 "image/color" 10 "image/draw" 11 "math" 12 "sync" 13 14 "github.com/BurntSushi/xgb/render" 15 "github.com/BurntSushi/xgb/xproto" 16 17 "github.com/as/shiny/math/f64" 18 "github.com/as/shiny/screen" 19 ) 20 21 const textureDepth = 32 22 23 type textureImpl struct { 24 s *screenImpl 25 26 size image.Point 27 xm xproto.Pixmap 28 xp render.Picture 29 30 // renderMu is a mutex that enforces the atomicity of methods like 31 // Window.Draw that are conceptually one operation but are implemented by 32 // multiple X11/Render calls. X11/Render is a stateful API, so interleaving 33 // X11/Render calls from separate higher-level operations causes 34 // inconsistencies. 35 renderMu sync.Mutex 36 37 releasedMu sync.Mutex 38 released bool 39 } 40 41 func (t *textureImpl) degenerate() bool { return t.size.X == 0 || t.size.Y == 0 } 42 func (t *textureImpl) Size() image.Point { return t.size } 43 func (t *textureImpl) Bounds() image.Rectangle { return image.Rectangle{Max: t.size} } 44 45 func (t *textureImpl) Release() { 46 t.releasedMu.Lock() 47 released := t.released 48 t.released = true 49 t.releasedMu.Unlock() 50 51 if released || t.degenerate() { 52 return 53 } 54 render.FreePicture(t.s.xc, t.xp) 55 xproto.FreePixmap(t.s.xc, t.xm) 56 } 57 58 func (t *textureImpl) Upload(dp image.Point, src screen.Buffer, sr image.Rectangle) { 59 if t.degenerate() { 60 return 61 } 62 src.(*bufferImpl).upload(xproto.Drawable(t.xm), t.s.gcontext32, textureDepth, dp, sr) 63 } 64 65 func (t *textureImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) { 66 if t.degenerate() { 67 return 68 } 69 fill(t.s.xc, t.xp, dr, src, op) 70 } 71 72 // f64ToFixed converts from float64 to X11/Render's 16.16 fixed point. 73 func f64ToFixed(x float64) render.Fixed { 74 return render.Fixed(x * 65536) 75 } 76 77 func inv(x *f64.Aff3) f64.Aff3 { 78 invDet := 1 / (x[0]*x[4] - x[1]*x[3]) 79 return f64.Aff3{ 80 +x[4] * invDet, 81 -x[1] * invDet, 82 (x[1]*x[5] - x[2]*x[4]) * invDet, 83 -x[3] * invDet, 84 +x[0] * invDet, 85 (x[2]*x[3] - x[0]*x[5]) * invDet, 86 } 87 } 88 89 func (t *textureImpl) draw(xp render.Picture, src2dst *f64.Aff3, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) { 90 sr = sr.Intersect(t.Bounds()) 91 if sr.Empty() { 92 return 93 } 94 95 t.renderMu.Lock() 96 defer t.renderMu.Unlock() 97 98 // For simple copies and scales, the inverse matrix is trivial to compute, 99 // and we do not need the "Src becomes OutReverse plus Over" dance (see 100 // below). Thus, draw can be one render.SetPictureTransform call and then 101 // one render.Composite call, regardless of whether or not op is Src. 102 if src2dst[1] == 0 && src2dst[3] == 0 { 103 dstXMin := float64(sr.Min.X)*src2dst[0] + src2dst[2] 104 dstXMax := float64(sr.Max.X)*src2dst[0] + src2dst[2] 105 if dstXMin > dstXMax { 106 // TODO: check if this (and below) works when src2dst[0] < 0. 107 dstXMin, dstXMax = dstXMax, dstXMin 108 } 109 dXMin := int(math.Floor(dstXMin)) 110 dXMax := int(math.Ceil(dstXMax)) 111 112 dstYMin := float64(sr.Min.Y)*src2dst[4] + src2dst[5] 113 dstYMax := float64(sr.Max.Y)*src2dst[4] + src2dst[5] 114 if dstYMin > dstYMax { 115 // TODO: check if this (and below) works when src2dst[4] < 0. 116 dstYMin, dstYMax = dstYMax, dstYMin 117 } 118 dYMin := int(math.Floor(dstYMin)) 119 dYMax := int(math.Ceil(dstYMax)) 120 121 render.SetPictureTransform(t.s.xc, t.xp, render.Transform{ 122 f64ToFixed(1 / src2dst[0]), 0, 0, 123 0, f64ToFixed(1 / src2dst[4]), 0, 124 0, 0, 1 << 16, 125 }) 126 render.Composite(t.s.xc, renderOp(op), t.xp, 0, xp, 127 int16(sr.Min.X), int16(sr.Min.Y), // SrcX, SrcY, 128 0, 0, // MaskX, MaskY, 129 int16(dXMin), int16(dYMin), // DstX, DstY, 130 uint16(dXMax-dXMin), uint16(dYMax-dYMin), // Width, Height, 131 ) 132 return 133 } 134 135 // The X11/Render transform matrix maps from destination pixels to source 136 // pixels, so we invert src2dst. 137 dst2src := inv(src2dst) 138 render.SetPictureTransform(t.s.xc, t.xp, render.Transform{ 139 f64ToFixed(dst2src[0]), f64ToFixed(dst2src[1]), render.Fixed(sr.Min.X << 16), 140 f64ToFixed(dst2src[3]), f64ToFixed(dst2src[4]), render.Fixed(sr.Min.Y << 16), 141 0, 0, 1 << 16, 142 }) 143 144 points := trifanPoints(src2dst, sr) 145 if op == draw.Src { 146 // render.TriFan visits every dst-space pixel in the axis-aligned 147 // bounding box (AABB) containing the transformation of the sr 148 // rectangle in src-space to a quad in dst-space. 149 // 150 // render.TriFan is like render.Composite, except that the AABB is 151 // defined implicitly by the transformed triangle vertices instead of 152 // being passed explicitly as arguments. It implies the minimal AABB. 153 // 154 // In any case, for arbitrary src2dst affine transformations, which 155 // include rotations, this means that a naive render.TriFan call will 156 // affect those pixels inside the AABB but outside the quad. For the 157 // draw.Src operator, this means that pixels in that AABB can be 158 // incorrectly set to zero. 159 // 160 // Instead, we implement the draw.Src operator as two render.TriFan 161 // calls. The first one (using the PictOpOutReverse operator and a 162 // fully opaque source) clears the dst-space quad but leaves pixels 163 // outside that quad (but inside the AABB) untouched. The second one 164 // (using the PictOpOver operator and the texture t as source) fills in 165 // the quad and again does not touch the pixels outside. 166 // 167 // What X11/Render calls PictOpOutReverse is also known as dst-out. See 168 // http://www.w3.org/TR/SVGCompositing/examples/compop-porterduff-examples.png 169 // for a visualization. 170 render.TriFan(t.s.xc, render.PictOpOutReverse, t.s.opaqueP, xp, 0, 0, 0, points[:]) 171 } 172 render.TriFan(t.s.xc, render.PictOpOver, t.xp, xp, 0, 0, 0, points[:]) 173 } 174 175 func trifanPoints(src2dst *f64.Aff3, sr image.Rectangle) [4]render.Pointfix { 176 minX := float64(sr.Min.X) 177 maxX := float64(sr.Max.X) 178 minY := float64(sr.Min.Y) 179 maxY := float64(sr.Max.Y) 180 return [4]render.Pointfix{{ 181 f64ToFixed(src2dst[0]*minX + src2dst[1]*minY + src2dst[2]), 182 f64ToFixed(src2dst[3]*minX + src2dst[4]*minY + src2dst[5]), 183 }, { 184 f64ToFixed(src2dst[0]*maxX + src2dst[1]*minY + src2dst[2]), 185 f64ToFixed(src2dst[3]*maxX + src2dst[4]*minY + src2dst[5]), 186 }, { 187 f64ToFixed(src2dst[0]*maxX + src2dst[1]*maxY + src2dst[2]), 188 f64ToFixed(src2dst[3]*maxX + src2dst[4]*maxY + src2dst[5]), 189 }, { 190 f64ToFixed(src2dst[0]*minX + src2dst[1]*maxY + src2dst[2]), 191 f64ToFixed(src2dst[3]*minX + src2dst[4]*maxY + src2dst[5]), 192 }} 193 } 194 195 func renderOp(op draw.Op) byte { 196 if op == draw.Src { 197 return render.PictOpSrc 198 } 199 return render.PictOpOver 200 }