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  }