twitchapon-anim

Basic Twitchapon Receiver/Visuals
git clone git://bsandro.tech/twitchapon-anim
Log | Files | Refs | README | LICENSE

window.go (11895B)


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