292 lines
6.4 KiB
Go
292 lines
6.4 KiB
Go
|
package glfw
|
||
|
|
||
|
import (
|
||
|
"runtime"
|
||
|
"sync"
|
||
|
"time"
|
||
|
|
||
|
"fyne.io/fyne/v2"
|
||
|
"fyne.io/fyne/v2/internal/app"
|
||
|
"fyne.io/fyne/v2/internal/cache"
|
||
|
"fyne.io/fyne/v2/internal/driver/common"
|
||
|
"fyne.io/fyne/v2/internal/painter"
|
||
|
"fyne.io/fyne/v2/internal/scale"
|
||
|
)
|
||
|
|
||
|
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.Cond
|
||
|
flag bool
|
||
|
}
|
||
|
|
||
|
// channel for queuing functions on the main thread
|
||
|
var funcQueue = make(chan funcData)
|
||
|
var drawFuncQueue = make(chan drawData)
|
||
|
var run = &runFlag{Cond: sync.Cond{L: &sync.Mutex{}}}
|
||
|
var initOnce = &sync.Once{}
|
||
|
|
||
|
// Arrange that main.main runs on main thread.
|
||
|
func init() {
|
||
|
runtime.LockOSThread()
|
||
|
mainGoroutineID = goroutineID()
|
||
|
}
|
||
|
|
||
|
// 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.L.Lock()
|
||
|
running := !run.flag
|
||
|
run.L.Unlock()
|
||
|
|
||
|
if running {
|
||
|
f()
|
||
|
} else {
|
||
|
done := common.DonePool.Get().(chan struct{})
|
||
|
defer common.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 := common.DonePool.Get().(chan struct{})
|
||
|
defer common.DonePool.Put(done)
|
||
|
|
||
|
drawFuncQueue <- drawData{f: f, win: w, done: done}
|
||
|
<-done
|
||
|
}
|
||
|
|
||
|
// Preallocate to avoid allocations on every drawSingleFrame.
|
||
|
// Note that the capacity of this slice can only grow,
|
||
|
// but its length will never be longer than the total number of
|
||
|
// window canvases that are dirty on a single frame.
|
||
|
// So its memory impact should be negligible and does not
|
||
|
// need periodic shrinking.
|
||
|
var refreshingCanvases []fyne.Canvas
|
||
|
|
||
|
func (d *gLDriver) drawSingleFrame() {
|
||
|
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)
|
||
|
|
||
|
// cleanup refreshingCanvases slice
|
||
|
for i := 0; i < len(refreshingCanvases); i++ {
|
||
|
refreshingCanvases[i] = nil
|
||
|
}
|
||
|
refreshingCanvases = refreshingCanvases[:0]
|
||
|
}
|
||
|
|
||
|
func (d *gLDriver) runGL() {
|
||
|
eventTick := time.NewTicker(time.Second / 60)
|
||
|
|
||
|
run.L.Lock()
|
||
|
run.flag = true
|
||
|
run.L.Unlock()
|
||
|
run.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.pollEvents()
|
||
|
windowsToRemove := 0
|
||
|
for _, win := range d.windowList() {
|
||
|
w := win.(*window)
|
||
|
if w.viewport == nil {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if w.viewport.ShouldClose() {
|
||
|
windowsToRemove++
|
||
|
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)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if drawOnMainThread {
|
||
|
d.drawSingleFrame()
|
||
|
}
|
||
|
}
|
||
|
if windowsToRemove > 0 {
|
||
|
oldWindows := d.windowList()
|
||
|
newWindows := make([]fyne.Window, 0, len(oldWindows)-windowsToRemove)
|
||
|
|
||
|
for _, win := range oldWindows {
|
||
|
w := win.(*window)
|
||
|
if w.viewport == nil {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if w.viewport.ShouldClose() {
|
||
|
w.viewLock.Lock()
|
||
|
w.visible = false
|
||
|
v := w.viewport
|
||
|
w.viewLock.Unlock()
|
||
|
|
||
|
// remove window from window list
|
||
|
v.Destroy()
|
||
|
w.destroy(d)
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
newWindows = append(newWindows, win)
|
||
|
}
|
||
|
|
||
|
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 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(scale.ToScreenCoordinate(canvas, size.Width)) * canvas.texScale
|
||
|
winHeight := float32(scale.ToScreenCoordinate(canvas, size.Height)) * canvas.texScale
|
||
|
|
||
|
canvas.Painter().SetFrameBufferScale(canvas.texScale)
|
||
|
w.canvas.Painter().SetOutputSize(int(winWidth), int(winHeight))
|
||
|
}
|