diff --git a/internal/ui/monitor_glfw.go b/internal/ui/monitor_glfw.go new file mode 100644 index 000000000..c2edabe03 --- /dev/null +++ b/internal/ui/monitor_glfw.go @@ -0,0 +1,126 @@ +// Copyright 2023 The Ebitengine Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !android && !ios && !js && !nintendosdk + +package ui + +import ( + "image" + "sync" + "sync/atomic" + + "github.com/hajimehoshi/ebiten/v2/internal/devicescale" + "github.com/hajimehoshi/ebiten/v2/internal/glfw" +) + +// Monitor is a wrapper around glfw.Monitor. +type Monitor struct { + m *glfw.Monitor + vm *glfw.VidMode + // Pos of monitor in virtual coords + x int + y int + width int + height int + id int + name string +} + +// Bounds returns the monitor's bounds. +func (m *Monitor) Bounds() image.Rectangle { + ui := Get() + return image.Rect( + int(ui.dipFromGLFWMonitorPixel(float64(m.x), m.m)), + int(ui.dipFromGLFWMonitorPixel(float64(m.y), m.m)), + int(ui.dipFromGLFWMonitorPixel(float64(m.x+m.width), m.m)), + int(ui.dipFromGLFWMonitorPixel(float64(m.x+m.height), m.m)), + ) +} + +// Name returns the monitor's name. +func (m *Monitor) Name() string { + return m.name +} + +type monitors struct { + // monitors is the monitor list cache for desktop glfw compile targets. + // populated by 'updateMonitors' which is called on init and every + // monitor config change event. + monitors []*Monitor + + m sync.Mutex + + updateCalled int32 +} + +var theMonitors monitors + +func (m *monitors) append(ms []*Monitor) []*Monitor { + if atomic.LoadInt32(&m.updateCalled) == 0 { + m.update() + } + + m.m.Lock() + defer m.m.Unlock() + + return append(ms, m.monitors...) +} + +func (m *monitors) monitorFromGLFWMonitor(glfwMonitor *glfw.Monitor) *Monitor { + m.m.Lock() + defer m.m.Unlock() + + for _, m := range m.monitors { + if m.m == glfwMonitor { + return m + } + } + return nil +} + +func (m *monitors) monitorFromID(id int) *Monitor { + m.m.Lock() + defer m.m.Unlock() + + return m.monitors[id] +} + +func (m *monitors) update() { + glfwMonitors := glfw.GetMonitors() + newMonitors := make([]*Monitor, 0, len(glfwMonitors)) + for i, m := range glfwMonitors { + x, y := m.GetPos() + vm := m.GetVideoMode() + newMonitors = append(newMonitors, &Monitor{ + m: m, + vm: m.GetVideoMode(), + x: x, + y: y, + width: vm.Width, + height: vm.Height, + name: m.GetName(), + id: i, + }) + } + + m.m.Lock() + m.monitors = newMonitors + m.m.Unlock() + + clearVideoModeScaleCache() + devicescale.ClearCache() + + atomic.StoreInt32(&m.updateCalled, 1) +} diff --git a/internal/ui/ui_glfw.go b/internal/ui/ui_glfw.go index 571cd0b2e..9468b4f44 100644 --- a/internal/ui/ui_glfw.go +++ b/internal/ui/ui_glfw.go @@ -155,9 +155,9 @@ func init() { panic(err) } glfw.SetMonitorCallback(glfw.ToMonitorCallback(func(monitor *glfw.Monitor, event glfw.PeripheralEvent) { - updateMonitors() + theMonitors.update() })) - updateMonitors() + theMonitors.update() } var glfwSystemCursors = map[CursorShape]*glfw.Cursor{} @@ -210,81 +210,9 @@ func (u *userInterfaceImpl) setInitMonitor(m *glfw.Monitor) { u.initFullscreenHeightInDIP = int(u.dipFromGLFWMonitorPixel(float64(v.Height), m)) } -// Monitor is a wrapper around glfw.Monitor. -type Monitor struct { - m *glfw.Monitor - vm *glfw.VidMode - // Pos of monitor in virtual coords - x int - y int - width int - height int - id int - name string -} - -// Bounds returns the monitor's bounds. -func (m *Monitor) Bounds() image.Rectangle { - ui := Get() - return image.Rect( - int(ui.dipFromGLFWMonitorPixel(float64(m.x), m.m)), - int(ui.dipFromGLFWMonitorPixel(float64(m.y), m.m)), - int(ui.dipFromGLFWMonitorPixel(float64(m.x+m.width), m.m)), - int(ui.dipFromGLFWMonitorPixel(float64(m.x+m.height), m.m)), - ) -} - -// Name returns the monitor's name. -func (m *Monitor) Name() string { - return m.name -} - -var ( - // monitors is the monitor list cache for desktop glfw compile targets. - // populated by 'updateMonitors' which is called on init and every - // monitor config change event. - // - // monitors must be manipulated on the main thread. - monitors []*Monitor - - monitorsM sync.Mutex - - updateMonitorsCalled int32 -) - -func appendMonitors(ms []*Monitor) []*Monitor { - if atomic.LoadInt32(&updateMonitorsCalled) == 0 { - updateMonitors() - } - - monitorsM.Lock() - defer monitorsM.Unlock() - - return append(ms, monitors...) -} - -func monitorFromGLFWMonitor(glfwMonitor *glfw.Monitor) *Monitor { - monitorsM.Lock() - defer monitorsM.Unlock() - - for _, m := range monitors { - if m.m == glfwMonitor { - return m - } - } - return nil -} - -func monitorFromID(id int) *Monitor { - monitorsM.Lock() - defer monitorsM.Unlock() - - return monitors[id] -} - // AppendMonitors appends the current monitors to the passed in mons slice and returns it. func (u *userInterfaceImpl) AppendMonitors(monitors []*Monitor) []*Monitor { - return appendMonitors(monitors) + return theMonitors.append(monitors) } // Monitor returns the window's current monitor. Returns nil if there is no current monitor yet. @@ -301,50 +229,17 @@ func (u *userInterfaceImpl) Monitor() *Monitor { if glfwMonitor == nil { return } - monitor = monitorFromGLFWMonitor(glfwMonitor) + monitor = theMonitors.monitorFromGLFWMonitor(glfwMonitor) }) return monitor } -func updateMonitors() { - glfwMonitors := glfw.GetMonitors() - newMonitors := make([]*Monitor, 0, len(glfwMonitors)) - for i, m := range glfwMonitors { - monitor := glfwMonitorToMonitor(m) - monitor.id = i - newMonitors = append(newMonitors, &monitor) - } - - monitorsM.Lock() - monitors = newMonitors - monitorsM.Unlock() - - clearVideoModeScaleCache() - devicescale.ClearCache() - - atomic.StoreInt32(&updateMonitorsCalled, 1) -} - -func glfwMonitorToMonitor(m *glfw.Monitor) Monitor { - x, y := m.GetPos() - vm := m.GetVideoMode() - return Monitor{ - m: m, - vm: m.GetVideoMode(), - x: x, - y: y, - width: vm.Width, - height: vm.Height, - name: m.GetName(), - } -} - // getMonitorFromPosition returns a monitor for the given window x/y, // or returns nil if monitor is not found. // // getMonitorFromPosition must be called on the main thread. func getMonitorFromPosition(wx, wy int) *Monitor { - for _, m := range appendMonitors(nil) { + for _, m := range theMonitors.append(nil) { // TODO: Fix incorrectness in the cases of https://github.com/glfw/glfw/issues/1961. // See also internal/devicescale/impl_desktop.go for a maybe better way of doing this. if m.x <= wx && wx < m.x+m.vm.Width && m.y <= wy && wy < m.y+m.vm.Height { @@ -380,7 +275,7 @@ func (u *userInterfaceImpl) setWindowMonitor(monitor int) { return } - m := monitorFromID(monitor).m + m := theMonitors.monitorFromID(monitor).m // Ignore if it is the same monitor. if m == u.currentMonitor() { @@ -897,7 +792,7 @@ func (u *userInterfaceImpl) createWindow(width, height int, monitor int) error { // Set our target monitor if provided. This is required to prevent an initial window flash on the default monitor. if monitor != glfw.DontCare { - m := monitorFromID(monitor) + m := theMonitors.monitorFromID(monitor) x, y := m.m.GetPos() px, py := InitialWindowPosition(m.m.GetVideoMode().Width, m.m.GetVideoMode().Height, width, height) window.SetPos(x+px, y+py) @@ -1093,7 +988,7 @@ func (u *userInterfaceImpl) initOnMainThread(options *RunOptions) error { monitor := u.getInitWindowMonitor() if monitor != glfw.DontCare { - u.setInitMonitor(monitorFromID(monitor).m) + u.setInitMonitor(theMonitors.monitorFromID(monitor).m) } ww, wh := u.getInitWindowSizeInDIP() diff --git a/internal/ui/ui_glfw_darwin.go b/internal/ui/ui_glfw_darwin.go index 868063a37..519600088 100644 --- a/internal/ui/ui_glfw_darwin.go +++ b/internal/ui/ui_glfw_darwin.go @@ -256,7 +256,7 @@ func initialMonitorByOS() (*glfw.Monitor, error) { x, y := currentMouseLocation() // Find the monitor including the cursor. - for _, m := range appendMonitors(nil) { + for _, m := range theMonitors.append(nil) { w, h := m.vm.Width, m.vm.Height if x >= m.x && x < m.x+w && y >= m.y && y < m.y+h { return m.m, nil @@ -279,7 +279,7 @@ func monitorFromWindowByOS(w *glfw.Window) *glfw.Monitor { screenID := cocoa.NSNumber{ID: screenDictionary.ObjectForKey(cocoa.NSString_alloc().InitWithUTF8String("NSScreenNumber").ID)} aID := uintptr(screenID.UnsignedIntValue()) // CGDirectDisplayID pool.Release() - for _, m := range appendMonitors(nil) { + for _, m := range theMonitors.append(nil) { if m.m.GetCocoaMonitor() == aID { return m.m } diff --git a/internal/ui/ui_glfw_linbsd.go b/internal/ui/ui_glfw_linbsd.go index 211d81812..59eb6cc85 100644 --- a/internal/ui/ui_glfw_linbsd.go +++ b/internal/ui/ui_glfw_linbsd.go @@ -176,7 +176,7 @@ func initialMonitorByOS() (*glfw.Monitor, error) { x, y := int(rep.RootX), int(rep.RootY) // Find the monitor including the cursor. - for _, m := range appendMonitors(nil) { + for _, m := range theMonitors.append(nil) { w, h := m.vm.Width, m.vm.Height if x >= m.x && x < m.x+w && y >= m.y && y < m.y+h { return m.m, nil diff --git a/internal/ui/ui_glfw_windows.go b/internal/ui/ui_glfw_windows.go index dd342ee78..6482281ca 100644 --- a/internal/ui/ui_glfw_windows.go +++ b/internal/ui/ui_glfw_windows.go @@ -140,7 +140,7 @@ func initialMonitorByOS() (*glfw.Monitor, error) { x, y := int(px), int(py) // Find the monitor including the cursor. - for _, m := range appendMonitors(nil) { + for _, m := range theMonitors.append(nil) { w, h := m.vm.Width, m.vm.Height if x >= m.x && x < m.x+w && y >= m.y && y < m.y+h { return m.m, nil @@ -173,7 +173,7 @@ func monitorFromWin32Window(w windows.HWND) *glfw.Monitor { } x, y := int(mi.rcMonitor.left), int(mi.rcMonitor.top) - for _, m := range appendMonitors(nil) { + for _, m := range theMonitors.append(nil) { if m.x == x && m.y == y { return m.m }