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 }