github.com/as/shiny@v0.8.2/driver/gldriver/window.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  // +build darwin
     6  
     7  package gldriver
     8  
     9  import (
    10  	"image"
    11  	"image/color"
    12  	"image/draw"
    13  	"sync"
    14  
    15  	"github.com/as/shiny/driver/internal/drawer"
    16  	"github.com/as/shiny/event/size"
    17  	"github.com/as/shiny/gl"
    18  	"github.com/as/shiny/math/f64"
    19  	"github.com/as/shiny/screen"
    20  )
    21  
    22  type windowImpl struct {
    23  	s *screenImpl
    24  
    25  	// id is an OS-specific data structure for the window.
    26  	id uintptr
    27  
    28  	// ctx is a C data structure for the GL context.
    29  	//	- Cocoa:   uintptr holding a NSOpenGLContext*.
    30  	ctx interface{}
    31  
    32  	publish     chan struct{}
    33  	publishDone chan screen.PublishResult
    34  	drawDone    chan struct{}
    35  
    36  	// glctxMu is a mutex that enforces the atomicity of methods like
    37  	// Texture.Upload or Window.Draw that are conceptually one operation
    38  	// but are implemented by multiple OpenGL calls. OpenGL is a stateful
    39  	// API, so interleaving OpenGL calls from separate higher-level
    40  	// operations causes inconsistencies.
    41  	glctxMu sync.Mutex
    42  	glctx   gl.Context
    43  	worker  gl.Worker
    44  	// backBufferBound is whether the default Framebuffer, with ID 0, also
    45  	// known as the back buffer or the window's Framebuffer, is bound and its
    46  	// viewport is known to equal the window size. It can become false when we
    47  	// bind to a texture's Framebuffer or when the window size changes.
    48  	backBufferBound bool
    49  
    50  	// szMu protects only sz. If you need to hold both glctxMu and szMu, the
    51  	// lock ordering is to lock glctxMu first (and unlock it last).
    52  	szMu sync.Mutex
    53  	sz   size.Event
    54  }
    55  
    56  func (w *windowImpl) Release() {
    57  	closeWindow(w.id)
    58  }
    59  
    60  func (w *windowImpl) Device() *screen.Device {
    61  	return screen.Dev
    62  
    63  }
    64  func (w *windowImpl) Upload(dp image.Point, src screen.Buffer, sr image.Rectangle) {
    65  	if sr.Empty() {
    66  		return
    67  	}
    68  	{
    69  		src := src.(*bufferImpl)
    70  		if src.t == nil || src.t.Size() != src.Size() {
    71  			t, err := w.s.NewTexture(src.Size())
    72  			if err != nil {
    73  				panic(err)
    74  			}
    75  			if src.t != nil {
    76  				src.t.Release()
    77  			}
    78  			src.t = t
    79  		} else {
    80  		}
    81  		src.t.Upload(sr.Min, src, sr)
    82  		dp = dp.Sub(sr.Min)
    83  		w.Draw(f64.Aff3{
    84  			1, 0, float64(dp.X),
    85  			0, 1, float64(dp.Y),
    86  		}, src.t, sr, draw.Src, nil)
    87  	}
    88  }
    89  
    90  func useOp(glctx gl.Context, op draw.Op) {
    91  	if op == draw.Over {
    92  		glctx.Enable(gl.BLEND)
    93  		glctx.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
    94  	} else {
    95  		glctx.Disable(gl.BLEND)
    96  	}
    97  }
    98  
    99  func (w *windowImpl) bindBackBuffer() {
   100  	return
   101  	if w.backBufferBound {
   102  		return
   103  	}
   104  	w.szMu.Lock()
   105  	sz := w.sz
   106  	w.szMu.Unlock()
   107  
   108  	w.backBufferBound = true
   109  	w.glctx.BindFramebuffer(gl.FRAMEBUFFER, gl.Framebuffer{Value: 0})
   110  	w.glctx.Viewport(0, 0, sz.WidthPx, sz.HeightPx)
   111  }
   112  
   113  func (w *windowImpl) fill(mvp f64.Aff3, src color.Color, op draw.Op) {
   114  	w.glctxMu.Lock()
   115  	defer w.glctxMu.Unlock()
   116  
   117  	//w.bindBackBuffer()
   118  
   119  	doFill(w.s, w.glctx, mvp, src, op)
   120  }
   121  
   122  func doFill(s *screenImpl, glctx gl.Context, mvp f64.Aff3, src color.Color, op draw.Op) {
   123  	useOp(glctx, op)
   124  	if !glctx.IsProgram(s.fill.program) {
   125  		p, err := compileProgram(glctx, fillVertexSrc, fillFragmentSrc)
   126  		if err != nil {
   127  			// TODO: initialize this somewhere else we can better handle the error.
   128  			panic(err.Error())
   129  		}
   130  		s.fill.program = p
   131  		s.fill.pos = glctx.GetAttribLocation(p, "pos")
   132  		s.fill.mvp = glctx.GetUniformLocation(p, "mvp")
   133  		s.fill.color = glctx.GetUniformLocation(p, "color")
   134  		s.fill.quad = glctx.CreateBuffer()
   135  
   136  		glctx.BindBuffer(gl.ARRAY_BUFFER, s.fill.quad)
   137  		glctx.BufferData(gl.ARRAY_BUFFER, quadCoords, gl.STATIC_DRAW)
   138  	}
   139  	glctx.UseProgram(s.fill.program)
   140  
   141  	writeAff3(glctx, s.fill.mvp, mvp)
   142  
   143  	r, g, b, a := src.RGBA()
   144  	glctx.Uniform4f(
   145  		s.fill.color,
   146  		float32(r)/65535,
   147  		float32(g)/65535,
   148  		float32(b)/65535,
   149  		float32(a)/65535,
   150  	)
   151  
   152  	glctx.BindBuffer(gl.ARRAY_BUFFER, s.fill.quad)
   153  	glctx.EnableVertexAttribArray(s.fill.pos)
   154  	glctx.VertexAttribPointer(s.fill.pos, 2, gl.FLOAT, false, 0, 0)
   155  
   156  	glctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
   157  
   158  	glctx.DisableVertexAttribArray(s.fill.pos)
   159  }
   160  
   161  func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {
   162  	minX := float64(dr.Min.X)
   163  	minY := float64(dr.Min.Y)
   164  	maxX := float64(dr.Max.X)
   165  	maxY := float64(dr.Max.Y)
   166  	w.fill(w.mvp(
   167  		minX, minY,
   168  		maxX, minY,
   169  		minX, maxY,
   170  	), src, op)
   171  }
   172  
   173  func (w *windowImpl) DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
   174  	minX := float64(sr.Min.X)
   175  	minY := float64(sr.Min.Y)
   176  	maxX := float64(sr.Max.X)
   177  	maxY := float64(sr.Max.Y)
   178  	w.fill(w.mvp(
   179  		src2dst[0]*minX+src2dst[1]*minY+src2dst[2],
   180  		src2dst[3]*minX+src2dst[4]*minY+src2dst[5],
   181  		src2dst[0]*maxX+src2dst[1]*minY+src2dst[2],
   182  		src2dst[3]*maxX+src2dst[4]*minY+src2dst[5],
   183  		src2dst[0]*minX+src2dst[1]*maxY+src2dst[2],
   184  		src2dst[3]*minX+src2dst[4]*maxY+src2dst[5],
   185  	), src, op)
   186  }
   187  
   188  func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
   189  	t := src.(*textureImpl)
   190  	sr = sr.Intersect(t.Bounds())
   191  	if sr.Empty() {
   192  		return
   193  	}
   194  
   195  	w.glctxMu.Lock()
   196  	defer w.glctxMu.Unlock()
   197  
   198  	//if !w.backBufferBound {
   199  	w.bindBackBuffer()
   200  	//}
   201  
   202  	useOp(w.glctx, op)
   203  	w.glctx.UseProgram(w.s.texture.program)
   204  
   205  	// Start with src-space left, top, right and bottom.
   206  	srcL := float64(sr.Min.X)
   207  	srcT := float64(sr.Min.Y)
   208  	srcR := float64(sr.Max.X)
   209  	srcB := float64(sr.Max.Y)
   210  	// Transform to dst-space via the src2dst matrix, then to a MVP matrix.
   211  	writeAff3(w.glctx, w.s.texture.mvp, w.mvp(
   212  		src2dst[0]*srcL+src2dst[1]*srcT+src2dst[2],
   213  		src2dst[3]*srcL+src2dst[4]*srcT+src2dst[5],
   214  		src2dst[0]*srcR+src2dst[1]*srcT+src2dst[2],
   215  		src2dst[3]*srcR+src2dst[4]*srcT+src2dst[5],
   216  		src2dst[0]*srcL+src2dst[1]*srcB+src2dst[2],
   217  		src2dst[3]*srcL+src2dst[4]*srcB+src2dst[5],
   218  	))
   219  
   220  	// OpenGL's fragment shaders' UV coordinates run from (0,0)-(1,1),
   221  	// unlike vertex shaders' XY coordinates running from (-1,+1)-(+1,-1).
   222  	//
   223  	// We are drawing a rectangle PQRS, defined by two of its
   224  	// corners, onto the entire texture. The two quads may actually
   225  	// be equal, but in the general case, PQRS can be smaller.
   226  	//
   227  	//	(0,0) +---------------+ (1,0)
   228  	//	      |  P +-----+ Q  |
   229  	//	      |    |     |    |
   230  	//	      |  S +-----+ R  |
   231  	//	(0,1) +---------------+ (1,1)
   232  	//
   233  	// The PQRS quad is always axis-aligned. First of all, convert
   234  	// from pixel space to texture space.
   235  	tw := float64(t.size.X)
   236  	th := float64(t.size.Y)
   237  	px := float64(sr.Min.X-0) / tw
   238  	py := float64(sr.Min.Y-0) / th
   239  	qx := float64(sr.Max.X-0) / tw
   240  	sy := float64(sr.Max.Y-0) / th
   241  	// Due to axis alignment, qy = py and sx = px.
   242  	//
   243  	// The simultaneous equations are:
   244  	//	  0 +   0 + a02 = px
   245  	//	  0 +   0 + a12 = py
   246  	//	a00 +   0 + a02 = qx
   247  	//	a10 +   0 + a12 = qy = py
   248  	//	  0 + a01 + a02 = sx = px
   249  	//	  0 + a11 + a12 = sy
   250  	writeAff3(w.glctx, w.s.texture.uvp, f64.Aff3{
   251  		qx - px, 0, px,
   252  		0, sy - py, py,
   253  	})
   254  
   255  	w.glctx.ActiveTexture(gl.TEXTURE0)
   256  	w.glctx.BindTexture(gl.TEXTURE_2D, t.id)
   257  	w.glctx.Uniform1i(w.s.texture.sample, 0)
   258  
   259  	w.glctx.BindBuffer(gl.ARRAY_BUFFER, w.s.texture.quad)
   260  	w.glctx.EnableVertexAttribArray(w.s.texture.pos)
   261  	w.glctx.VertexAttribPointer(w.s.texture.pos, 2, gl.FLOAT, false, 0, 0)
   262  
   263  	w.glctx.BindBuffer(gl.ARRAY_BUFFER, w.s.texture.quad)
   264  	w.glctx.EnableVertexAttribArray(w.s.texture.inUV)
   265  	w.glctx.VertexAttribPointer(w.s.texture.inUV, 2, gl.FLOAT, false, 0, 0)
   266  
   267  	w.glctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
   268  
   269  	w.glctx.DisableVertexAttribArray(w.s.texture.pos)
   270  	w.glctx.DisableVertexAttribArray(w.s.texture.inUV)
   271  }
   272  
   273  func (w *windowImpl) Copy(dp image.Point, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
   274  	drawer.Copy(w, dp, src, sr, op, opts)
   275  }
   276  
   277  func (w *windowImpl) Scale(dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
   278  	drawer.Scale(w, dr, src, sr, op, opts)
   279  }
   280  
   281  func (w *windowImpl) mvp(tlx, tly, trx, try, blx, bly float64) f64.Aff3 {
   282  	w.szMu.Lock()
   283  	sz := w.sz
   284  	w.szMu.Unlock()
   285  
   286  	return calcMVP(sz.WidthPx, sz.HeightPx, tlx, tly, trx, try, blx, bly)
   287  }
   288  
   289  // calcMVP returns the Model View Projection matrix that maps the quadCoords
   290  // unit square, (0, 0) to (1, 1), to a quad QV, such that QV in vertex shader
   291  // space corresponds to the quad QP in pixel space, where QP is defined by
   292  // three of its four corners - the arguments to this function. The three
   293  // corners are nominally the top-left, top-right and bottom-left, but there is
   294  // no constraint that e.g. tlx < trx.
   295  //
   296  // In pixel space, the window ranges from (0, 0) to (widthPx, heightPx). The
   297  // Y-axis points downwards.
   298  //
   299  // In vertex shader space, the window ranges from (-1, +1) to (+1, -1), which
   300  // is a 2-unit by 2-unit square. The Y-axis points upwards.
   301  func calcMVP(widthPx, heightPx int, tlx, tly, trx, try, blx, bly float64) f64.Aff3 {
   302  	// Convert from pixel coords to vertex shader coords.
   303  	invHalfWidth := +2 / float64(widthPx)
   304  	invHalfHeight := -2 / float64(heightPx)
   305  	tlx = tlx*invHalfWidth - 1
   306  	tly = tly*invHalfHeight + 1
   307  	trx = trx*invHalfWidth - 1
   308  	try = try*invHalfHeight + 1
   309  	blx = blx*invHalfWidth - 1
   310  	bly = bly*invHalfHeight + 1
   311  
   312  	// The resultant affine matrix:
   313  	//	- maps (0, 0) to (tlx, tly).
   314  	//	- maps (1, 0) to (trx, try).
   315  	//	- maps (0, 1) to (blx, bly).
   316  	return f64.Aff3{
   317  		trx - tlx, blx - tlx, tlx,
   318  		try - tly, bly - tly, tly,
   319  	}
   320  }
   321  
   322  func (w *windowImpl) Publish() screen.PublishResult {
   323  	// gl.Flush is a lightweight (on modern GL drivers) blocking call
   324  	// that ensures all GL functions pending in the gl package have
   325  	// been passed onto the GL driver before the app package attempts
   326  	// to swap the screen buffer.
   327  	//
   328  	// This enforces that the final receive (for this paint cycle) on
   329  	// gl.WorkAvailable happens before the send on publish.
   330  	//w.glctxMu.Lock()
   331  	w.glctx.Flush()
   332  	//w.glctxMu.Unlock()
   333  
   334  	w.publish <- struct{}{}
   335  	res := <-w.publishDone
   336  
   337  	select {
   338  	case w.drawDone <- struct{}{}:
   339  	default:
   340  	}
   341  
   342  	return res
   343  }