github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/exp/shiny/driver/gldriver/x11.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 linux,!android 6 7 package gldriver 8 9 /* 10 #cgo LDFLAGS: -lEGL -lGLESv2 -lX11 11 12 #include <stdint.h> 13 14 void startDriver(); 15 void processEvents(); 16 void makeCurrent(uintptr_t ctx); 17 void swapBuffers(uintptr_t ctx); 18 uintptr_t doNewWindow(int width, int height); 19 uintptr_t doShowWindow(uintptr_t id); 20 */ 21 import "C" 22 import ( 23 "runtime" 24 "time" 25 26 "golang.org/x/exp/shiny/driver/internal/x11key" 27 "golang.org/x/exp/shiny/screen" 28 "golang.org/x/mobile/event/mouse" 29 "golang.org/x/mobile/event/paint" 30 "golang.org/x/mobile/event/size" 31 "golang.org/x/mobile/geom" 32 "golang.org/x/mobile/gl" 33 ) 34 35 func init() { 36 // It might not be necessary, but it probably doesn't hurt to try to make 37 // 'the main thread' be 'the X11 / OpenGL thread'. 38 runtime.LockOSThread() 39 } 40 41 func newWindow(width, height int32) uintptr { 42 retc := make(chan uintptr) 43 uic <- uiClosure{ 44 f: func() uintptr { 45 return uintptr(C.doNewWindow(C.int(width), C.int(height))) 46 }, 47 retc: retc, 48 } 49 return <-retc 50 } 51 52 func showWindow(w *windowImpl) { 53 retc := make(chan uintptr) 54 uic <- uiClosure{ 55 f: func() uintptr { 56 return uintptr(C.doShowWindow(C.uintptr_t(w.id))) 57 }, 58 retc: retc, 59 } 60 w.ctx = <-retc 61 w.glctxMu.Lock() 62 w.glctx, w.worker = glctx, worker 63 w.glctxMu.Unlock() 64 go drawLoop(w) 65 } 66 67 func closeWindow(id uintptr) { 68 // TODO. 69 } 70 71 func drawLoop(w *windowImpl) { 72 glcontextc <- w.ctx 73 go func() { 74 for range w.publish { 75 publishc <- w 76 } 77 }() 78 } 79 80 var ( 81 glcontextc = make(chan uintptr) 82 publishc = make(chan *windowImpl) 83 uic = make(chan uiClosure) 84 85 // TODO: don't assume that there is only one window, and hence only 86 // one (global) GL context. 87 // 88 // TODO: should we be able to make a shiny.Texture before having a 89 // shiny.Window's GL context? Should something like gl.IsProgram be a 90 // method instead of a function, and have each shiny.Window have its own 91 // gl.Context? 92 glctx gl.Context 93 worker gl.Worker 94 ) 95 96 // uiClosure is a closure to be run on C's UI thread. 97 type uiClosure struct { 98 f func() uintptr 99 retc chan uintptr 100 } 101 102 func main(f func(screen.Screen)) error { 103 C.startDriver() 104 glctx, worker = gl.NewContext() 105 106 closec := make(chan struct{}) 107 go func() { 108 f(theScreen) 109 close(closec) 110 }() 111 112 // heartbeat is a channel that, at regular intervals, directs the select 113 // below to also consider X11 events, not just Go events (channel 114 // communications). 115 // 116 // TODO: select instead of poll. Note that knowing whether to call 117 // C.processEvents needs to select on a file descriptor, and the other 118 // cases below select on Go channels. 119 heartbeat := time.NewTicker(time.Second / 60) 120 workAvailable := worker.WorkAvailable() 121 122 for { 123 select { 124 case <-closec: 125 return nil 126 case ctx := <-glcontextc: 127 // TODO: do we need to synchronize with seeing a size event for 128 // this window's context before or after calling makeCurrent? 129 // Otherwise, are we racing with the gl.Viewport call? I've 130 // occasionally seen a stale viewport, if the window manager sets 131 // the window width and height to something other than that 132 // requested by XCreateWindow, but it's not easily reproducible. 133 C.makeCurrent(C.uintptr_t(ctx)) 134 case w := <-publishc: 135 C.swapBuffers(C.uintptr_t(w.ctx)) 136 w.publishDone <- screen.PublishResult{} 137 case req := <-uic: 138 req.retc <- req.f() 139 case <-heartbeat.C: 140 C.processEvents() 141 case <-workAvailable: 142 worker.DoWork() 143 } 144 } 145 } 146 147 //export onExpose 148 func onExpose(id uintptr) { 149 theScreen.mu.Lock() 150 w := theScreen.windows[id] 151 theScreen.mu.Unlock() 152 153 if w == nil { 154 return 155 } 156 157 w.Send(paint.Event{External: true}) 158 } 159 160 //export onMouse 161 func onMouse(id uintptr, x, y, state, button, dir int32) { 162 theScreen.mu.Lock() 163 w := theScreen.windows[id] 164 theScreen.mu.Unlock() 165 166 if w == nil { 167 return 168 } 169 170 // TODO: should a mouse.Event have a separate MouseModifiers field, for 171 // which buttons are pressed during a mouse move? 172 w.Send(mouse.Event{ 173 X: float32(x), 174 Y: float32(y), 175 Button: mouse.Button(button), 176 Modifiers: x11key.KeyModifiers(uint16(state)), 177 Direction: mouse.Direction(dir), 178 }) 179 } 180 181 //export onResize 182 func onResize(id uintptr, width, height int32) { 183 theScreen.mu.Lock() 184 w := theScreen.windows[id] 185 theScreen.mu.Unlock() 186 187 if w == nil { 188 return 189 } 190 191 // TODO: should this really be done on the receiving end of the w.Events() 192 // channel, in the same goroutine as other GL calls in the app's 'business 193 // logic'? 194 go func() { 195 w.glctxMu.Lock() 196 w.glctx.Viewport(0, 0, int(width), int(height)) 197 w.glctxMu.Unlock() 198 }() 199 200 sz := size.Event{ 201 WidthPx: int(width), 202 HeightPx: int(height), 203 WidthPt: geom.Pt(width), 204 HeightPt: geom.Pt(height), 205 // TODO: don't assume 72 DPI. DisplayWidth and DisplayWidthMM is 206 // probably the best place to start looking. 207 PixelsPerPt: 1, 208 } 209 210 w.szMu.Lock() 211 w.sz = sz 212 w.szMu.Unlock() 213 214 w.Send(sz) 215 216 // TODO: lifecycle events? 217 }