loop.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. package glfw
  2. import (
  3. "runtime"
  4. "sync"
  5. "time"
  6. "fyne.io/fyne/v2"
  7. "fyne.io/fyne/v2/internal/app"
  8. "fyne.io/fyne/v2/internal/cache"
  9. "fyne.io/fyne/v2/internal/driver/common"
  10. "fyne.io/fyne/v2/internal/painter"
  11. "fyne.io/fyne/v2/internal/scale"
  12. )
  13. type funcData struct {
  14. f func()
  15. done chan struct{} // Zero allocation signalling channel
  16. }
  17. type drawData struct {
  18. f func()
  19. win *window
  20. done chan struct{} // Zero allocation signalling channel
  21. }
  22. type runFlag struct {
  23. sync.Cond
  24. flag bool
  25. }
  26. // channel for queuing functions on the main thread
  27. var funcQueue = make(chan funcData)
  28. var drawFuncQueue = make(chan drawData)
  29. var run = &runFlag{Cond: sync.Cond{L: &sync.Mutex{}}}
  30. var initOnce = &sync.Once{}
  31. // Arrange that main.main runs on main thread.
  32. func init() {
  33. runtime.LockOSThread()
  34. mainGoroutineID = goroutineID()
  35. }
  36. // force a function f to run on the main thread
  37. func runOnMain(f func()) {
  38. // If we are on main just execute - otherwise add it to the main queue and wait.
  39. // The "running" variable is normally false when we are on the main thread.
  40. run.L.Lock()
  41. running := !run.flag
  42. run.L.Unlock()
  43. if running {
  44. f()
  45. } else {
  46. done := common.DonePool.Get().(chan struct{})
  47. defer common.DonePool.Put(done)
  48. funcQueue <- funcData{f: f, done: done}
  49. <-done
  50. }
  51. }
  52. // force a function f to run on the draw thread
  53. func runOnDraw(w *window, f func()) {
  54. if drawOnMainThread {
  55. runOnMain(func() { w.RunWithContext(f) })
  56. return
  57. }
  58. done := common.DonePool.Get().(chan struct{})
  59. defer common.DonePool.Put(done)
  60. drawFuncQueue <- drawData{f: f, win: w, done: done}
  61. <-done
  62. }
  63. // Preallocate to avoid allocations on every drawSingleFrame.
  64. // Note that the capacity of this slice can only grow,
  65. // but its length will never be longer than the total number of
  66. // window canvases that are dirty on a single frame.
  67. // So its memory impact should be negligible and does not
  68. // need periodic shrinking.
  69. var refreshingCanvases []fyne.Canvas
  70. func (d *gLDriver) drawSingleFrame() {
  71. for _, win := range d.windowList() {
  72. w := win.(*window)
  73. w.viewLock.RLock()
  74. canvas := w.canvas
  75. closing := w.closing
  76. visible := w.visible
  77. w.viewLock.RUnlock()
  78. // CheckDirtyAndClear must be checked after visibility,
  79. // because when a window becomes visible, it could be
  80. // showing old content without a dirty flag set to true.
  81. // Do the clear if and only if the window is visible.
  82. if closing || !visible || !canvas.CheckDirtyAndClear() {
  83. continue
  84. }
  85. d.repaintWindow(w)
  86. refreshingCanvases = append(refreshingCanvases, canvas)
  87. }
  88. cache.CleanCanvases(refreshingCanvases)
  89. // cleanup refreshingCanvases slice
  90. for i := 0; i < len(refreshingCanvases); i++ {
  91. refreshingCanvases[i] = nil
  92. }
  93. refreshingCanvases = refreshingCanvases[:0]
  94. }
  95. func (d *gLDriver) runGL() {
  96. eventTick := time.NewTicker(time.Second / 60)
  97. run.L.Lock()
  98. run.flag = true
  99. run.L.Unlock()
  100. run.Broadcast()
  101. d.initGLFW()
  102. if d.trayStart != nil {
  103. d.trayStart()
  104. }
  105. fyne.CurrentApp().Lifecycle().(*app.Lifecycle).TriggerStarted()
  106. for {
  107. select {
  108. case <-d.done:
  109. eventTick.Stop()
  110. d.drawDone <- nil // wait for draw thread to stop
  111. d.Terminate()
  112. fyne.CurrentApp().Lifecycle().(*app.Lifecycle).TriggerStopped()
  113. return
  114. case f := <-funcQueue:
  115. f.f()
  116. if f.done != nil {
  117. f.done <- struct{}{}
  118. }
  119. case <-eventTick.C:
  120. d.tryPollEvents()
  121. newWindows := []fyne.Window{}
  122. reassign := false
  123. for _, win := range d.windowList() {
  124. w := win.(*window)
  125. if w.viewport == nil {
  126. continue
  127. }
  128. if w.viewport.ShouldClose() {
  129. reassign = true
  130. w.viewLock.Lock()
  131. w.visible = false
  132. v := w.viewport
  133. w.viewLock.Unlock()
  134. // remove window from window list
  135. v.Destroy()
  136. w.destroy(d)
  137. continue
  138. }
  139. w.viewLock.RLock()
  140. expand := w.shouldExpand
  141. fullScreen := w.fullScreen
  142. w.viewLock.RUnlock()
  143. if expand && !fullScreen {
  144. w.fitContent()
  145. w.viewLock.Lock()
  146. shouldExpand := w.shouldExpand
  147. w.shouldExpand = false
  148. view := w.viewport
  149. w.viewLock.Unlock()
  150. if shouldExpand {
  151. view.SetSize(w.shouldWidth, w.shouldHeight)
  152. }
  153. }
  154. newWindows = append(newWindows, win)
  155. if drawOnMainThread {
  156. d.drawSingleFrame()
  157. }
  158. }
  159. if reassign {
  160. d.windowLock.Lock()
  161. d.windows = newWindows
  162. d.windowLock.Unlock()
  163. if len(newWindows) == 0 {
  164. d.Quit()
  165. }
  166. }
  167. }
  168. }
  169. }
  170. func (d *gLDriver) repaintWindow(w *window) {
  171. canvas := w.canvas
  172. w.RunWithContext(func() {
  173. if canvas.EnsureMinSize() {
  174. w.viewLock.Lock()
  175. w.shouldExpand = true
  176. w.viewLock.Unlock()
  177. }
  178. canvas.FreeDirtyTextures()
  179. updateGLContext(w)
  180. canvas.paint(canvas.Size())
  181. w.viewLock.RLock()
  182. view := w.viewport
  183. visible := w.visible
  184. w.viewLock.RUnlock()
  185. if view != nil && visible {
  186. view.SwapBuffers()
  187. }
  188. })
  189. }
  190. func (d *gLDriver) startDrawThread() {
  191. settingsChange := make(chan fyne.Settings)
  192. fyne.CurrentApp().Settings().AddChangeListener(settingsChange)
  193. var drawCh <-chan time.Time
  194. if drawOnMainThread {
  195. drawCh = make(chan time.Time) // don't tick when on M1
  196. } else {
  197. drawCh = time.NewTicker(time.Second / 60).C
  198. }
  199. go func() {
  200. runtime.LockOSThread()
  201. for {
  202. select {
  203. case <-d.drawDone:
  204. return
  205. case f := <-drawFuncQueue:
  206. f.win.RunWithContext(f.f)
  207. if f.done != nil {
  208. f.done <- struct{}{}
  209. }
  210. case set := <-settingsChange:
  211. painter.ClearFontCache()
  212. cache.ResetThemeCaches()
  213. app.ApplySettingsWithCallback(set, fyne.CurrentApp(), func(w fyne.Window) {
  214. c, ok := w.Canvas().(*glCanvas)
  215. if !ok {
  216. return
  217. }
  218. c.applyThemeOutOfTreeObjects()
  219. go c.reloadScale()
  220. })
  221. case <-drawCh:
  222. d.drawSingleFrame()
  223. }
  224. }
  225. }()
  226. }
  227. // refreshWindow requests that the specified window be redrawn
  228. func refreshWindow(w *window) {
  229. w.canvas.SetDirty()
  230. }
  231. func updateGLContext(w *window) {
  232. canvas := w.Canvas().(*glCanvas)
  233. size := canvas.Size()
  234. // w.width and w.height are not correct if we are maximised, so figure from canvas
  235. winWidth := float32(scale.ToScreenCoordinate(canvas, size.Width)) * canvas.texScale
  236. winHeight := float32(scale.ToScreenCoordinate(canvas, size.Height)) * canvas.texScale
  237. canvas.Painter().SetFrameBufferScale(canvas.texScale)
  238. w.canvas.Painter().SetOutputSize(int(winWidth), int(winHeight))
  239. }