| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- package glfw
- import (
- "runtime"
- "sync"
- "time"
- "fyne.io/fyne/v2"
- "fyne.io/fyne/v2/internal"
- "fyne.io/fyne/v2/internal/app"
- "fyne.io/fyne/v2/internal/cache"
- "fyne.io/fyne/v2/internal/painter"
- )
- type funcData struct {
- f func()
- done chan struct{} // Zero allocation signalling channel
- }
- type drawData struct {
- f func()
- win *window
- done chan struct{} // Zero allocation signalling channel
- }
- type runFlag struct {
- sync.Mutex
- flag bool
- cond *sync.Cond
- }
- // channel for queuing functions on the main thread
- var funcQueue = make(chan funcData)
- var drawFuncQueue = make(chan drawData)
- var run *runFlag
- var initOnce = &sync.Once{}
- var donePool = &sync.Pool{New: func() interface{} {
- return make(chan struct{})
- }}
- func newRun() *runFlag {
- r := runFlag{}
- r.cond = sync.NewCond(&r)
- return &r
- }
- // Arrange that main.main runs on main thread.
- func init() {
- runtime.LockOSThread()
- mainGoroutineID = goroutineID()
- run = newRun()
- }
- // force a function f to run on the main thread
- func runOnMain(f func()) {
- // If we are on main just execute - otherwise add it to the main queue and wait.
- // The "running" variable is normally false when we are on the main thread.
- run.Lock()
- if !run.flag {
- f()
- run.Unlock()
- } else {
- run.Unlock()
- done := donePool.Get().(chan struct{})
- defer donePool.Put(done)
- funcQueue <- funcData{f: f, done: done}
- <-done
- }
- }
- // force a function f to run on the draw thread
- func runOnDraw(w *window, f func()) {
- if drawOnMainThread {
- runOnMain(func() { w.RunWithContext(f) })
- return
- }
- done := donePool.Get().(chan struct{})
- defer donePool.Put(done)
- drawFuncQueue <- drawData{f: f, win: w, done: done}
- <-done
- }
- func (d *gLDriver) drawSingleFrame() {
- refreshingCanvases := make([]fyne.Canvas, 0)
- for _, win := range d.windowList() {
- w := win.(*window)
- w.viewLock.RLock()
- canvas := w.canvas
- closing := w.closing
- visible := w.visible
- w.viewLock.RUnlock()
- // CheckDirtyAndClear must be checked after visibility,
- // because when a window becomes visible, it could be
- // showing old content without a dirty flag set to true.
- // Do the clear if and only if the window is visible.
- if closing || !visible || !canvas.CheckDirtyAndClear() {
- continue
- }
- d.repaintWindow(w)
- refreshingCanvases = append(refreshingCanvases, canvas)
- }
- cache.CleanCanvases(refreshingCanvases)
- }
- func (d *gLDriver) runGL() {
- eventTick := time.NewTicker(time.Second / 60)
- run.Lock()
- run.flag = true
- run.Unlock()
- run.cond.Broadcast()
- d.initGLFW()
- if d.trayStart != nil {
- d.trayStart()
- }
- fyne.CurrentApp().Lifecycle().(*app.Lifecycle).TriggerStarted()
- for {
- select {
- case <-d.done:
- eventTick.Stop()
- d.drawDone <- nil // wait for draw thread to stop
- d.Terminate()
- fyne.CurrentApp().Lifecycle().(*app.Lifecycle).TriggerStopped()
- return
- case f := <-funcQueue:
- f.f()
- if f.done != nil {
- f.done <- struct{}{}
- }
- case <-eventTick.C:
- d.tryPollEvents()
- newWindows := []fyne.Window{}
- reassign := false
- for _, win := range d.windowList() {
- w := win.(*window)
- if w.viewport == nil {
- continue
- }
- if w.viewport.ShouldClose() {
- reassign = true
- w.viewLock.Lock()
- w.visible = false
- v := w.viewport
- w.viewLock.Unlock()
- // remove window from window list
- v.Destroy()
- w.destroy(d)
- continue
- }
- w.viewLock.RLock()
- expand := w.shouldExpand
- fullScreen := w.fullScreen
- w.viewLock.RUnlock()
- if expand && !fullScreen {
- w.fitContent()
- w.viewLock.Lock()
- shouldExpand := w.shouldExpand
- w.shouldExpand = false
- view := w.viewport
- w.viewLock.Unlock()
- if shouldExpand {
- view.SetSize(w.shouldWidth, w.shouldHeight)
- }
- }
- newWindows = append(newWindows, win)
- if drawOnMainThread {
- d.drawSingleFrame()
- }
- }
- if reassign {
- d.windowLock.Lock()
- d.windows = newWindows
- d.windowLock.Unlock()
- if len(newWindows) == 0 {
- d.Quit()
- }
- }
- }
- }
- }
- func (d *gLDriver) repaintWindow(w *window) {
- canvas := w.canvas
- w.RunWithContext(func() {
- if w.canvas.EnsureMinSize() {
- w.viewLock.Lock()
- w.shouldExpand = true
- w.viewLock.Unlock()
- }
- canvas.FreeDirtyTextures()
- updateGLContext(w)
- canvas.paint(canvas.Size())
- w.viewLock.RLock()
- view := w.viewport
- visible := w.visible
- w.viewLock.RUnlock()
- if view != nil && visible {
- view.SwapBuffers()
- }
- })
- }
- func (d *gLDriver) startDrawThread() {
- settingsChange := make(chan fyne.Settings)
- fyne.CurrentApp().Settings().AddChangeListener(settingsChange)
- var drawCh <-chan time.Time
- if drawOnMainThread {
- drawCh = make(chan time.Time) // don't tick when on M1
- } else {
- drawCh = time.NewTicker(time.Second / 60).C
- }
- go func() {
- runtime.LockOSThread()
- for {
- select {
- case <-d.drawDone:
- return
- case f := <-drawFuncQueue:
- f.win.RunWithContext(f.f)
- if f.done != nil {
- f.done <- struct{}{}
- }
- case set := <-settingsChange:
- painter.ClearFontCache()
- cache.ResetThemeCaches()
- app.ApplySettingsWithCallback(set, fyne.CurrentApp(), func(w fyne.Window) {
- c, ok := w.Canvas().(*glCanvas)
- if !ok {
- return
- }
- c.applyThemeOutOfTreeObjects()
- go c.reloadScale()
- })
- case <-drawCh:
- d.drawSingleFrame()
- }
- }
- }()
- }
- // refreshWindow requests that the specified window be redrawn
- func refreshWindow(w *window) {
- w.canvas.SetDirty()
- }
- func updateGLContext(w *window) {
- canvas := w.Canvas().(*glCanvas)
- size := canvas.Size()
- // w.width and w.height are not correct if we are maximised, so figure from canvas
- winWidth := float32(internal.ScaleInt(canvas, size.Width)) * canvas.texScale
- winHeight := float32(internal.ScaleInt(canvas, size.Height)) * canvas.texScale
- canvas.Painter().SetFrameBufferScale(canvas.texScale)
- w.canvas.Painter().SetOutputSize(int(winWidth), int(winHeight))
- }
|