github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/exp/shiny/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 package gldriver 6 7 import ( 8 "image" 9 "image/color" 10 "image/draw" 11 "sync" 12 13 "golang.org/x/exp/shiny/driver/internal/pump" 14 "golang.org/x/exp/shiny/screen" 15 "golang.org/x/image/math/f64" 16 "golang.org/x/mobile/event/lifecycle" 17 "golang.org/x/mobile/event/size" 18 "golang.org/x/mobile/gl" 19 ) 20 21 type windowImpl struct { 22 s *screenImpl 23 24 // id is a C data structure for the window. 25 // - For Cocoa, it's a ScreenGLView*. 26 // - For X11, it's a Window. 27 id uintptr 28 29 // ctx is a C data structure for the GL context. 30 // - For Cocoa, it's a NSOpenGLContext*. 31 // - For X11, it's an EGLSurface. 32 ctx uintptr 33 34 lifecycleStage lifecycle.Stage // current stage 35 36 pump pump.Pump 37 publish chan struct{} 38 publishDone chan screen.PublishResult 39 drawDone chan struct{} 40 41 // glctxMu is a mutex that enforces the atomicity of methods like 42 // Texture.Upload or Window.Draw that are conceptually one operation 43 // but are implemented by multiple OpenGL calls. OpenGL is a stateful 44 // API, so interleaving OpenGL calls from separate higher-level 45 // operations causes inconsistencies. 46 glctxMu sync.Mutex 47 glctx gl.Context 48 worker gl.Worker 49 50 szMu sync.Mutex 51 sz size.Event 52 } 53 54 func (w *windowImpl) Release() { 55 // There are two ways a window can be closed. The first is the user 56 // clicks the red button, in which case windowWillClose is called, 57 // which calls Go's windowClosing, which does cleanup in 58 // releaseCleanup below. 59 // 60 // The second way is Release is called programmatically. This calls 61 // the NSWindow method performClose, which emulates the red button 62 // being clicked. 63 // 64 // If these two approaches race, experiments suggest it is resolved 65 // by performClose (which is called serially on the main thread). 66 // If that stops being true, there is a check in windowWillClose 67 // that avoids the Go cleanup code being invoked more than once. 68 closeWindow(w.id) 69 } 70 71 func (w *windowImpl) releaseCleanup() { 72 w.pump.Release() 73 } 74 75 func (w *windowImpl) Events() <-chan interface{} { return w.pump.Events() } 76 func (w *windowImpl) Send(event interface{}) { w.pump.Send(event) } 77 78 func (w *windowImpl) Upload(dp image.Point, src screen.Buffer, sr image.Rectangle) { 79 // TODO: adjust if dp is outside dst bounds, or sr is outside src bounds. 80 // TODO: keep a texture around for this purpose? 81 t, err := w.s.NewTexture(sr.Size()) 82 if err != nil { 83 panic(err) 84 } 85 t.Upload(dp, src, sr) 86 w.Draw(f64.Aff3{ 87 1, 0, float64(dp.X), 88 0, 1, float64(dp.Y), 89 }, t, sr, draw.Src, nil) 90 t.Release() 91 } 92 93 func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) { 94 w.glctxMu.Lock() 95 defer w.glctxMu.Unlock() 96 97 if !w.glctx.IsProgram(w.s.fill.program) { 98 p, err := compileProgram(w.glctx, fillVertexSrc, fillFragmentSrc) 99 if err != nil { 100 // TODO: initialize this somewhere else we can better handle the error. 101 panic(err.Error()) 102 } 103 w.s.fill.program = p 104 w.s.fill.pos = w.glctx.GetAttribLocation(p, "pos") 105 w.s.fill.mvp = w.glctx.GetUniformLocation(p, "mvp") 106 w.s.fill.color = w.glctx.GetUniformLocation(p, "color") 107 w.s.fill.quad = w.glctx.CreateBuffer() 108 109 w.glctx.BindBuffer(gl.ARRAY_BUFFER, w.s.fill.quad) 110 w.glctx.BufferData(gl.ARRAY_BUFFER, quadCoords, gl.STATIC_DRAW) 111 } 112 w.glctx.UseProgram(w.s.fill.program) 113 114 dstL := float64(dr.Min.X) 115 dstT := float64(dr.Min.Y) 116 dstR := float64(dr.Max.X) 117 dstB := float64(dr.Max.Y) 118 writeAff3(w.glctx, w.s.fill.mvp, w.mvp( 119 dstL, dstT, 120 dstR, dstT, 121 dstL, dstB, 122 )) 123 124 r, g, b, a := src.RGBA() 125 w.glctx.Uniform4f( 126 w.s.fill.color, 127 float32(r)/65535, 128 float32(g)/65535, 129 float32(b)/65535, 130 float32(a)/65535, 131 ) 132 133 w.glctx.BindBuffer(gl.ARRAY_BUFFER, w.s.fill.quad) 134 w.glctx.EnableVertexAttribArray(w.s.fill.pos) 135 w.glctx.VertexAttribPointer(w.s.fill.pos, 2, gl.FLOAT, false, 0, 0) 136 137 w.glctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4) 138 139 w.glctx.DisableVertexAttribArray(w.s.fill.pos) 140 } 141 142 func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) { 143 w.glctxMu.Lock() 144 defer w.glctxMu.Unlock() 145 146 w.glctx.UseProgram(w.s.texture.program) 147 148 // Start with src-space left, top, right and bottom. 149 srcL := float64(sr.Min.X) 150 srcT := float64(sr.Min.Y) 151 srcR := float64(sr.Max.X) 152 srcB := float64(sr.Max.Y) 153 // Transform to dst-space via the src2dst matrix, then to a MVP matrix. 154 writeAff3(w.glctx, w.s.texture.mvp, w.mvp( 155 src2dst[0]*srcL+src2dst[1]*srcT+src2dst[2], 156 src2dst[3]*srcL+src2dst[4]*srcT+src2dst[5], 157 src2dst[0]*srcR+src2dst[1]*srcT+src2dst[2], 158 src2dst[3]*srcR+src2dst[4]*srcT+src2dst[5], 159 src2dst[0]*srcL+src2dst[1]*srcB+src2dst[2], 160 src2dst[3]*srcL+src2dst[4]*srcB+src2dst[5], 161 )) 162 163 // OpenGL's fragment shaders' UV coordinates run from (0,0)-(1,1), 164 // unlike vertex shaders' XY coordinates running from (-1,+1)-(+1,-1). 165 // 166 // We are drawing a rectangle PQRS, defined by two of its 167 // corners, onto the entire texture. The two quads may actually 168 // be equal, but in the general case, PQRS can be smaller. 169 // 170 // (0,0) +---------------+ (1,0) 171 // | P +-----+ Q | 172 // | | | | 173 // | S +-----+ R | 174 // (0,1) +---------------+ (1,1) 175 // 176 // The PQRS quad is always axis-aligned. First of all, convert 177 // from pixel space to texture space. 178 t := src.(*textureImpl) 179 tw := float64(t.size.X) 180 th := float64(t.size.Y) 181 px := float64(sr.Min.X-0) / tw 182 py := float64(sr.Min.Y-0) / th 183 qx := float64(sr.Max.X-0) / tw 184 sy := float64(sr.Max.Y-0) / th 185 // Due to axis alignment, qy = py and sx = px. 186 // 187 // The simultaneous equations are: 188 // 0 + 0 + a02 = px 189 // 0 + 0 + a12 = py 190 // a00 + 0 + a02 = qx 191 // a10 + 0 + a12 = qy = py 192 // 0 + a01 + a02 = sx = px 193 // 0 + a11 + a12 = sy 194 writeAff3(w.glctx, w.s.texture.uvp, f64.Aff3{ 195 qx - px, 0, px, 196 0, sy - py, py, 197 }) 198 199 w.glctx.ActiveTexture(gl.TEXTURE0) 200 w.glctx.BindTexture(gl.TEXTURE_2D, t.id) 201 w.glctx.Uniform1i(w.s.texture.sample, 0) 202 203 w.glctx.BindBuffer(gl.ARRAY_BUFFER, w.s.texture.quad) 204 w.glctx.EnableVertexAttribArray(w.s.texture.pos) 205 w.glctx.VertexAttribPointer(w.s.texture.pos, 2, gl.FLOAT, false, 0, 0) 206 207 w.glctx.BindBuffer(gl.ARRAY_BUFFER, w.s.texture.quad) 208 w.glctx.EnableVertexAttribArray(w.s.texture.inUV) 209 w.glctx.VertexAttribPointer(w.s.texture.inUV, 2, gl.FLOAT, false, 0, 0) 210 211 w.glctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4) 212 213 w.glctx.DisableVertexAttribArray(w.s.texture.pos) 214 w.glctx.DisableVertexAttribArray(w.s.texture.inUV) 215 } 216 217 // mvp returns the Model View Projection matrix that maps the quadCoords unit 218 // square, (0, 0) to (1, 1), to a quad QV, such that QV in vertex shader space 219 // corresponds to the quad QP in pixel space, where QP is defined by three of 220 // its four corners - the arguments to this function. The three corners are 221 // nominally the top-left, top-right and bottom-left, but there is no 222 // constraint that e.g. tlx < trx. 223 // 224 // In pixel space, the window ranges from (0, 0) to (sz.WidthPx, sz.HeightPx). 225 // The Y-axis points downwards. 226 // 227 // In vertex shader space, the window ranges from (-1, +1) to (+1, -1), which 228 // is a 2-unit by 2-unit square. The Y-axis points upwards. 229 func (w *windowImpl) mvp(tlx, tly, trx, try, blx, bly float64) f64.Aff3 { 230 w.szMu.Lock() 231 sz := w.sz 232 w.szMu.Unlock() 233 234 // Convert from pixel coords to vertex shader coords. 235 invHalfWidth := +2 / float64(sz.WidthPx) 236 invHalfHeight := -2 / float64(sz.HeightPx) 237 tlx = tlx*invHalfWidth - 1 238 tly = tly*invHalfHeight + 1 239 trx = trx*invHalfWidth - 1 240 try = try*invHalfHeight + 1 241 blx = blx*invHalfWidth - 1 242 bly = bly*invHalfHeight + 1 243 244 // The resultant affine matrix: 245 // - maps (0, 0) to (tlx, tly). 246 // - maps (1, 0) to (trx, try). 247 // - maps (0, 1) to (blx, bly). 248 return f64.Aff3{ 249 trx - tlx, blx - tlx, tlx, 250 try - tly, bly - tly, tly, 251 } 252 } 253 254 func (w *windowImpl) Publish() screen.PublishResult { 255 // gl.Flush is a lightweight (on modern GL drivers) blocking call 256 // that ensures all GL functions pending in the gl package have 257 // been passed onto the GL driver before the app package attempts 258 // to swap the screen buffer. 259 // 260 // This enforces that the final receive (for this paint cycle) on 261 // gl.WorkAvailable happens before the send on publish. 262 w.glctxMu.Lock() 263 w.glctx.Flush() 264 w.glctxMu.Unlock() 265 266 w.publish <- struct{}{} 267 res := <-w.publishDone 268 269 select { 270 case w.drawDone <- struct{}{}: 271 default: 272 } 273 274 return res 275 }