diff --git a/internal/driver/graphics.go b/internal/driver/graphics.go index 849017163..1621a374a 100644 --- a/internal/driver/graphics.go +++ b/internal/driver/graphics.go @@ -17,9 +17,11 @@ package driver import ( "github.com/hajimehoshi/ebiten/internal/affine" "github.com/hajimehoshi/ebiten/internal/graphics" + "github.com/hajimehoshi/ebiten/internal/thread" ) type Graphics interface { + SetThread(thread *thread.Thread) Begin() End() SetWindow(window uintptr) diff --git a/internal/graphicsdriver/metal/driver.go b/internal/graphicsdriver/metal/driver.go index 9c5807eea..377b88d5b 100644 --- a/internal/graphicsdriver/metal/driver.go +++ b/internal/graphicsdriver/metal/driver.go @@ -26,7 +26,7 @@ import ( "github.com/hajimehoshi/ebiten/internal/graphics" "github.com/hajimehoshi/ebiten/internal/graphicsdriver/metal/ca" "github.com/hajimehoshi/ebiten/internal/graphicsdriver/metal/mtl" - "github.com/hajimehoshi/ebiten/internal/mainthread" + "github.com/hajimehoshi/ebiten/internal/thread" ) // #cgo CFLAGS: -x objective-c -mmacosx-version-min=10.11 @@ -301,6 +301,8 @@ type Driver struct { maxImageSize int + t *thread.Thread + pool unsafe.Pointer } @@ -310,8 +312,12 @@ func Get() *Driver { return &theDriver } +func (d *Driver) SetThread(thread *thread.Thread) { + d.t = thread +} + func (d *Driver) Begin() { - mainthread.Run(func() error { + d.t.Run(func() error { // NSAutoreleasePool is required to release drawable correctly (#847). // https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/MTLBestPracticesGuide/Drawables.html d.pool = C.allocAutoreleasePool() @@ -321,7 +327,7 @@ func (d *Driver) Begin() { func (d *Driver) End() { d.flush(false, true) - mainthread.Run(func() error { + d.t.Run(func() error { d.screenDrawable = ca.MetalDrawable{} C.releaseAutoreleasePool(d.pool) d.pool = nil @@ -330,7 +336,7 @@ func (d *Driver) End() { } func (d *Driver) SetWindow(window uintptr) { - mainthread.Run(func() error { + d.t.Run(func() error { // Note that [NSApp mainWindow] returns nil when the window is borderless. // Then the window is needed to be given. d.window = window @@ -339,7 +345,7 @@ func (d *Driver) SetWindow(window uintptr) { } func (d *Driver) SetVertices(vertices []float32, indices []uint16) { - mainthread.Run(func() error { + d.t.Run(func() error { if d.vb != (mtl.Buffer{}) { d.vb.Release() } @@ -357,7 +363,7 @@ func (d *Driver) Flush() { } func (d *Driver) flush(wait bool, present bool) { - mainthread.Run(func() error { + d.t.Run(func() error { if d.cb == (mtl.CommandBuffer{}) { return nil } @@ -378,7 +384,7 @@ func (d *Driver) flush(wait bool, present bool) { func (d *Driver) checkSize(width, height int) { m := 0 - mainthread.Run(func() error { + d.t.Run(func() error { if d.maxImageSize == 0 { d.maxImageSize = 4096 // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf @@ -438,7 +444,7 @@ func (d *Driver) NewImage(width, height int) (driver.Image, error) { Usage: mtl.TextureUsageShaderRead, } var t mtl.Texture - mainthread.Run(func() error { + d.t.Run(func() error { t = d.device.MakeTexture(td) return nil }) @@ -451,7 +457,7 @@ func (d *Driver) NewImage(width, height int) (driver.Image, error) { } func (d *Driver) NewScreenFramebufferImage(width, height int) (driver.Image, error) { - mainthread.Run(func() error { + d.t.Run(func() error { d.ml.SetDrawableSize(width, height) return nil }) @@ -464,7 +470,7 @@ func (d *Driver) NewScreenFramebufferImage(width, height int) (driver.Image, err } func (d *Driver) Reset() error { - if err := mainthread.Run(func() error { + if err := d.t.Run(func() error { if d.cq != (mtl.CommandQueue{}) { d.cq.Release() d.cq = mtl.CommandQueue{} @@ -606,7 +612,7 @@ func (d *Driver) Reset() error { } func (d *Driver) Draw(indexLen int, indexOffset int, mode graphics.CompositeMode, colorM *affine.ColorM, filter graphics.Filter, address graphics.Address) error { - if err := mainthread.Run(func() error { + if err := d.t.Run(func() error { // NSView can be changed anytime (probably). Set this everyframe. setView(d.window, d.ml) @@ -690,16 +696,16 @@ func (d *Driver) Draw(indexLen int, indexOffset int, mode graphics.CompositeMode } func (d *Driver) ResetSource() { - mainthread.Run(func() error { + d.t.Run(func() error { d.src = nil return nil }) } func (d *Driver) SetVsyncEnabled(enabled bool) { - // TODO: Now SetVsyncEnabled is called only from the main thread, and mainthread.Run is not available since + // TODO: Now SetVsyncEnabled is called only from the main thread, and d.t.Run is not available since // recursive function call via Run is forbidden. - // Fix this to use mainthread.Run to avoid confusion. + // Fix this to use d.t.Run to avoid confusion. d.ml.SetDisplaySyncEnabled(enabled) } @@ -732,7 +738,7 @@ func (i *Image) viewportSize() (int, int) { } func (i *Image) Dispose() { - mainthread.Run(func() error { + i.driver.t.Run(func() error { if i.texture != (mtl.Texture{}) { i.texture.Release() i.texture = mtl.Texture{} @@ -749,7 +755,7 @@ func (i *Image) IsInvalidated() bool { } func (i *Image) syncTexture() { - mainthread.Run(func() error { + i.driver.t.Run(func() error { if i.driver.cb != (mtl.CommandBuffer{}) { panic("metal: command buffer must be empty at syncTexture: flush is not called yet?") } @@ -769,7 +775,7 @@ func (i *Image) Pixels() ([]byte, error) { i.syncTexture() b := make([]byte, 4*i.width*i.height) - mainthread.Run(func() error { + i.driver.t.Run(func() error { i.texture.GetBytes(&b[0], uintptr(4*i.width), mtl.Region{ Size: mtl.Size{i.width, i.height, 1}, }, 0) @@ -779,14 +785,14 @@ func (i *Image) Pixels() ([]byte, error) { } func (i *Image) SetAsDestination() { - mainthread.Run(func() error { + i.driver.t.Run(func() error { i.driver.dst = i return nil }) } func (i *Image) SetAsSource() { - mainthread.Run(func() error { + i.driver.t.Run(func() error { i.driver.src = i return nil }) @@ -795,7 +801,7 @@ func (i *Image) SetAsSource() { func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) { i.driver.flush(true, false) - mainthread.Run(func() error { + i.driver.t.Run(func() error { i.texture.ReplaceRegion(mtl.Region{ Origin: mtl.Origin{x, y, 0}, Size: mtl.Size{width, height, 1}, diff --git a/internal/graphicsdriver/opengl/context.go b/internal/graphicsdriver/opengl/context.go index e56572855..660adef72 100644 --- a/internal/graphicsdriver/opengl/context.go +++ b/internal/graphicsdriver/opengl/context.go @@ -18,6 +18,7 @@ import ( "fmt" "github.com/hajimehoshi/ebiten/internal/graphics" + "github.com/hajimehoshi/ebiten/internal/thread" ) func convertOperation(op graphics.Operation) operation { @@ -48,6 +49,9 @@ type context struct { lastViewportHeight int lastCompositeMode graphics.CompositeMode maxTextureSize int + + t *thread.Thread + contextImpl } diff --git a/internal/graphicsdriver/opengl/context_desktop.go b/internal/graphicsdriver/opengl/context_desktop.go index e9349e7a3..e61b7770a 100644 --- a/internal/graphicsdriver/opengl/context_desktop.go +++ b/internal/graphicsdriver/opengl/context_desktop.go @@ -25,7 +25,6 @@ import ( "github.com/hajimehoshi/ebiten/internal/graphics" "github.com/hajimehoshi/ebiten/internal/graphicsdriver/opengl/gl" - "github.com/hajimehoshi/ebiten/internal/mainthread" ) type ( @@ -76,7 +75,7 @@ type contextImpl struct { } func (c *context) reset() error { - if err := mainthread.Run(func() error { + if err := c.t.Run(func() error { if c.init { return nil } @@ -95,12 +94,12 @@ func (c *context) reset() error { c.lastViewportWidth = 0 c.lastViewportHeight = 0 c.lastCompositeMode = graphics.CompositeModeUnknown - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { gl.Enable(gl.BLEND) return nil }) c.blendFunc(graphics.CompositeModeSourceOver) - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { f := int32(0) gl.GetIntegerv(gl.FRAMEBUFFER_BINDING, &f) c.screenFramebuffer = framebufferNative(f) @@ -110,7 +109,7 @@ func (c *context) reset() error { } func (c *context) blendFunc(mode graphics.CompositeMode) { - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { if c.lastCompositeMode == mode { return nil } @@ -124,7 +123,7 @@ func (c *context) blendFunc(mode graphics.CompositeMode) { func (c *context) newTexture(width, height int) (textureNative, error) { var texture textureNative - if err := mainthread.Run(func() error { + if err := c.t.Run(func() error { var t uint32 gl.GenTextures(1, &t) // TODO: Use gl.IsTexture @@ -138,7 +137,7 @@ func (c *context) newTexture(width, height int) (textureNative, error) { return 0, err } c.bindTexture(texture) - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) @@ -152,7 +151,7 @@ func (c *context) newTexture(width, height int) (textureNative, error) { } func (c *context) bindFramebufferImpl(f framebufferNative) { - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { gl.BindFramebufferEXT(gl.FRAMEBUFFER, uint32(f)) return nil }) @@ -160,12 +159,12 @@ func (c *context) bindFramebufferImpl(f framebufferNative) { func (c *context) framebufferPixels(f *framebuffer, width, height int) ([]byte, error) { var pixels []byte - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { gl.Flush() return nil }) c.bindFramebuffer(f.native) - if err := mainthread.Run(func() error { + if err := c.t.Run(func() error { pixels = make([]byte, 4*width*height) gl.ReadPixels(0, 0, int32(width), int32(height), gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(pixels)) return nil @@ -176,14 +175,14 @@ func (c *context) framebufferPixels(f *framebuffer, width, height int) ([]byte, } func (c *context) bindTextureImpl(t textureNative) { - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { gl.BindTexture(gl.TEXTURE_2D, uint32(t)) return nil }) } func (c *context) deleteTexture(t textureNative) { - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { tt := uint32(t) if !gl.IsTexture(tt) { return nil @@ -198,7 +197,7 @@ func (c *context) deleteTexture(t textureNative) { func (c *context) isTexture(t textureNative) bool { r := false - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { r = gl.IsTexture(uint32(t)) return nil }) @@ -207,7 +206,7 @@ func (c *context) isTexture(t textureNative) bool { func (c *context) texSubImage2D(t textureNative, p []byte, x, y, width, height int) { c.bindTexture(t) - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { gl.TexSubImage2D(gl.TEXTURE_2D, 0, int32(x), int32(y), int32(width), int32(height), gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(p)) return nil }) @@ -216,7 +215,7 @@ func (c *context) texSubImage2D(t textureNative, p []byte, x, y, width, height i func (c *context) newFramebuffer(texture textureNative) (framebufferNative, error) { var framebuffer framebufferNative var f uint32 - if err := mainthread.Run(func() error { + if err := c.t.Run(func() error { gl.GenFramebuffersEXT(1, &f) // TODO: Use gl.IsFramebuffer if f <= 0 { @@ -227,7 +226,7 @@ func (c *context) newFramebuffer(texture textureNative) (framebufferNative, erro return 0, err } c.bindFramebuffer(framebufferNative(f)) - if err := mainthread.Run(func() error { + if err := c.t.Run(func() error { gl.FramebufferTexture2DEXT(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, uint32(texture), 0) s := gl.CheckFramebufferStatusEXT(gl.FRAMEBUFFER) if s != gl.FRAMEBUFFER_COMPLETE { @@ -248,14 +247,14 @@ func (c *context) newFramebuffer(texture textureNative) (framebufferNative, erro } func (c *context) setViewportImpl(width, height int) { - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { gl.Viewport(0, 0, int32(width), int32(height)) return nil }) } func (c *context) deleteFramebuffer(f framebufferNative) { - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { ff := uint32(f) if !gl.IsFramebufferEXT(ff) { return nil @@ -272,7 +271,7 @@ func (c *context) deleteFramebuffer(f framebufferNative) { func (c *context) newShader(shaderType shaderType, source string) (shader, error) { var sh shader - if err := mainthread.Run(func() error { + if err := c.t.Run(func() error { s := gl.CreateShader(uint32(shaderType)) if s == 0 { return fmt.Errorf("opengl: glCreateShader failed: shader type: %d", shaderType) @@ -302,7 +301,7 @@ func (c *context) newShader(shaderType shaderType, source string) (shader, error } func (c *context) deleteShader(s shader) { - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { gl.DeleteShader(uint32(s)) return nil }) @@ -310,7 +309,7 @@ func (c *context) deleteShader(s shader) { func (c *context) newProgram(shaders []shader, attributes []string) (program, error) { var pr program - if err := mainthread.Run(func() error { + if err := c.t.Run(func() error { p := gl.CreateProgram() if p == 0 { return errors.New("opengl: glCreateProgram failed") @@ -341,14 +340,14 @@ func (c *context) newProgram(shaders []shader, attributes []string) (program, er } func (c *context) useProgram(p program) { - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { gl.UseProgram(uint32(p)) return nil }) } func (c *context) deleteProgram(p program) { - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { if !gl.IsProgram(uint32(p)) { return nil } @@ -368,7 +367,7 @@ func (c *context) getUniformLocationImpl(p program, location string) uniformLoca } func (c *context) uniformInt(p program, location string, v int) { - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { l := int32(c.locationCache.GetUniformLocation(c, p, location)) gl.Uniform1i(l, int32(v)) return nil @@ -376,7 +375,7 @@ func (c *context) uniformInt(p program, location string, v int) { } func (c *context) uniformFloat(p program, location string, v float32) { - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { l := int32(c.locationCache.GetUniformLocation(c, p, location)) gl.Uniform1f(l, v) return nil @@ -384,7 +383,7 @@ func (c *context) uniformFloat(p program, location string, v float32) { } func (c *context) uniformFloats(p program, location string, v []float32) { - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { l := int32(c.locationCache.GetUniformLocation(c, p, location)) switch len(v) { case 2: @@ -401,21 +400,21 @@ func (c *context) uniformFloats(p program, location string, v []float32) { } func (c *context) vertexAttribPointer(p program, index int, size int, dataType dataType, stride int, offset int) { - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { gl.VertexAttribPointer(uint32(index), int32(size), uint32(dataType), false, int32(stride), gl.PtrOffset(offset)) return nil }) } func (c *context) enableVertexAttribArray(p program, index int) { - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { gl.EnableVertexAttribArray(uint32(index)) return nil }) } func (c *context) disableVertexAttribArray(p program, index int) { - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { gl.DisableVertexAttribArray(uint32(index)) return nil }) @@ -423,7 +422,7 @@ func (c *context) disableVertexAttribArray(p program, index int) { func (c *context) newArrayBuffer(size int) buffer { var bf buffer - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { var b uint32 gl.GenBuffers(1, &b) gl.BindBuffer(uint32(arrayBuffer), b) @@ -436,7 +435,7 @@ func (c *context) newArrayBuffer(size int) buffer { func (c *context) newElementArrayBuffer(size int) buffer { var bf buffer - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { var b uint32 gl.GenBuffers(1, &b) gl.BindBuffer(uint32(elementArrayBuffer), b) @@ -448,28 +447,28 @@ func (c *context) newElementArrayBuffer(size int) buffer { } func (c *context) bindBuffer(bufferType bufferType, b buffer) { - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { gl.BindBuffer(uint32(bufferType), uint32(b)) return nil }) } func (c *context) arrayBufferSubData(data []float32) { - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { gl.BufferSubData(uint32(arrayBuffer), 0, len(data)*4, gl.Ptr(data)) return nil }) } func (c *context) elementArrayBufferSubData(data []uint16) { - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { gl.BufferSubData(uint32(elementArrayBuffer), 0, len(data)*2, gl.Ptr(data)) return nil }) } func (c *context) deleteBuffer(b buffer) { - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { bb := uint32(b) gl.DeleteBuffers(1, &bb) return nil @@ -477,7 +476,7 @@ func (c *context) deleteBuffer(b buffer) { } func (c *context) drawElements(len int, offsetInBytes int) { - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { gl.DrawElements(gl.TRIANGLES, int32(len), gl.UNSIGNED_SHORT, gl.PtrOffset(offsetInBytes)) return nil }) @@ -485,7 +484,7 @@ func (c *context) drawElements(len int, offsetInBytes int) { func (c *context) maxTextureSizeImpl() int { size := 0 - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { s := int32(0) gl.GetIntegerv(gl.MAX_TEXTURE_SIZE, &s) size = int(s) @@ -495,7 +494,7 @@ func (c *context) maxTextureSizeImpl() int { } func (c *context) flush() { - _ = mainthread.Run(func() error { + _ = c.t.Run(func() error { gl.Flush() return nil }) diff --git a/internal/graphicsdriver/opengl/driver.go b/internal/graphicsdriver/opengl/driver.go index 8250b16b7..a3f3d297b 100644 --- a/internal/graphicsdriver/opengl/driver.go +++ b/internal/graphicsdriver/opengl/driver.go @@ -20,6 +20,7 @@ import ( "github.com/hajimehoshi/ebiten/internal/affine" "github.com/hajimehoshi/ebiten/internal/driver" "github.com/hajimehoshi/ebiten/internal/graphics" + "github.com/hajimehoshi/ebiten/internal/thread" ) var theDriver Driver @@ -33,6 +34,10 @@ type Driver struct { context context } +func (d *Driver) SetThread(thread *thread.Thread) { + d.context.t = thread +} + func (d *Driver) Begin() { // Do nothing. } diff --git a/internal/mainthread/mainthread.go b/internal/thread/thread.go similarity index 54% rename from internal/mainthread/mainthread.go rename to internal/thread/thread.go index 6491e3ee3..c4c855ed4 100644 --- a/internal/mainthread/mainthread.go +++ b/internal/thread/thread.go @@ -12,30 +12,35 @@ // See the License for the specific language governing permissions and // limitations under the License. -package mainthread +package thread import ( - "runtime" "sync/atomic" ) -func init() { - runtime.LockOSThread() +// Thread represents an OS thread. +type Thread struct { + started int32 + funcs chan func() } -var ( - started = int32(0) - funcs = make(chan func()) -) - -// Loop starts the main-thread loop. +// New creates a new thread. // -// Loop must be called on the main thread. -func Loop(ch <-chan error) error { - atomic.StoreInt32(&started, 1) +// It is assumed that the OS thread is fixed by runtime.LockOSThread when New is called. +func New() *Thread { + return &Thread{ + funcs: make(chan func()), + } +} + +// Loop starts the thread loop. +// +// Loop must be called on the thread. +func (t *Thread) Loop(ch <-chan error) error { + atomic.StoreInt32(&t.started, 1) for { select { - case f := <-funcs: + case f := <-t.funcs: f() case err := <-ch: // ch returns a value not only when an error occur but also it is closed. @@ -44,18 +49,17 @@ func Loop(ch <-chan error) error { } } -// Run calls f on the main thread. +// Run calls f on the thread. // -// Do not call this from the main thread. This would block forever. -func Run(f func() error) error { - if atomic.LoadInt32(&started) == 0 { - // TODO: This can reach from other goroutine before Loop is called (#809). - // panic("mainthread: the mainthread loop is not started yet") +// Do not call this from the same thread. This would block forever. +func (t *Thread) Run(f func() error) error { + if atomic.LoadInt32(&t.started) == 0 { + panic("thread: the thread loop is not started yet") } ch := make(chan struct{}) var err error - funcs <- func() { + t.funcs <- func() { err = f() close(ch) } diff --git a/internal/uidriver/glfw/ui.go b/internal/uidriver/glfw/ui.go index 42b0dc05e..fd72c6048 100644 --- a/internal/uidriver/glfw/ui.go +++ b/internal/uidriver/glfw/ui.go @@ -29,7 +29,7 @@ import ( "github.com/hajimehoshi/ebiten/internal/devicescale" "github.com/hajimehoshi/ebiten/internal/driver" "github.com/hajimehoshi/ebiten/internal/glfw" - "github.com/hajimehoshi/ebiten/internal/mainthread" + "github.com/hajimehoshi/ebiten/internal/thread" ) type UserInterface struct { @@ -66,6 +66,7 @@ type UserInterface struct { graphics driver.Graphics input Input + t *thread.Thread m sync.Mutex } @@ -172,7 +173,7 @@ func getCachedMonitor(wx, wy int) (*cachedMonitor, bool) { func (u *UserInterface) mainThreadLoop(ch <-chan error) error { u.setRunning(true) - if err := mainthread.Loop(ch); err != nil { + if err := u.t.Loop(ch); err != nil { return err } u.setRunning(false) @@ -277,7 +278,7 @@ func (u *UserInterface) ScreenSizeInFullscreen() (int, int) { var v *glfw.VidMode s := 0.0 - _ = mainthread.Run(func() error { + _ = u.t.Run(func() error { v = u.currentMonitor().GetVideoMode() s = u.glfwScale() return nil @@ -289,7 +290,7 @@ func (u *UserInterface) SetScreenSize(width, height int) { if !u.isRunning() { panic("ui: Run is not called yet") } - _ = mainthread.Run(func() error { + _ = u.t.Run(func() error { // TODO: What if the window is maximized? (#320) u.setScreenSize(width, height, u.scale, u.isFullscreen(), u.vsync) return nil @@ -300,7 +301,7 @@ func (u *UserInterface) SetScreenScale(scale float64) { if !u.isRunning() { panic("ui: Run is not called yet") } - _ = mainthread.Run(func() error { + _ = u.t.Run(func() error { // TODO: What if the window is maximized? (#320) u.setScreenSize(u.width, u.height, scale, u.isFullscreen(), u.vsync) return nil @@ -312,7 +313,7 @@ func (u *UserInterface) ScreenScale() float64 { return 0 } s := 0.0 - _ = mainthread.Run(func() error { + _ = u.t.Run(func() error { s = u.scale return nil }) @@ -332,7 +333,7 @@ func (u *UserInterface) IsFullscreen() bool { return u.isInitFullscreen() } b := false - _ = mainthread.Run(func() error { + _ = u.t.Run(func() error { b = u.isFullscreen() return nil }) @@ -344,7 +345,7 @@ func (u *UserInterface) SetFullscreen(fullscreen bool) { u.setInitFullscreen(fullscreen) return } - _ = mainthread.Run(func() error { + _ = u.t.Run(func() error { u.setScreenSize(u.width, u.height, u.scale, fullscreen, u.vsync) return nil }) @@ -369,7 +370,7 @@ func (u *UserInterface) SetVsyncEnabled(enabled bool) { u.m.Unlock() return } - _ = mainthread.Run(func() error { + _ = u.t.Run(func() error { u.setScreenSize(u.width, u.height, u.scale, u.isFullscreen(), enabled) return nil }) @@ -386,7 +387,7 @@ func (u *UserInterface) SetWindowTitle(title string) { if !u.isRunning() { return } - _ = mainthread.Run(func() error { + _ = u.t.Run(func() error { u.window.SetTitle(title) return nil }) @@ -397,7 +398,7 @@ func (u *UserInterface) SetWindowIcon(iconImages []image.Image) { u.setInitIconImages(iconImages) return } - _ = mainthread.Run(func() error { + _ = u.t.Run(func() error { u.window.SetIcon(iconImages) return nil }) @@ -413,7 +414,7 @@ func (u *UserInterface) ScreenPadding() (x0, y0, x1, y1 float64) { } // The window width can be bigger than the game screen width (#444). ox := 0.0 - _ = mainthread.Run(func() error { + _ = u.t.Run(func() error { ox = (float64(u.windowWidth)*u.actualScreenScale() - float64(u.width)*u.actualScreenScale()) / 2 return nil }) @@ -426,7 +427,7 @@ func (u *UserInterface) ScreenPadding() (x0, y0, x1, y1 float64) { gs := 0.0 vw := 0.0 vh := 0.0 - _ = mainthread.Run(func() error { + _ = u.t.Run(func() error { m := u.window.GetMonitor() d = devicescale.GetAt(m.GetPos()) sx = float64(u.width) * u.actualScreenScale() @@ -451,7 +452,7 @@ func (u *UserInterface) adjustPosition(x, y int) (int, int) { } ox, oy, _, _ := u.ScreenPadding() s := 0.0 - _ = mainthread.Run(func() error { + _ = u.t.Run(func() error { s = u.actualScreenScale() return nil }) @@ -463,7 +464,7 @@ func (u *UserInterface) IsCursorVisible() bool { return u.isInitCursorVisible() } v := false - _ = mainthread.Run(func() error { + _ = u.t.Run(func() error { v = u.window.GetInputMode(glfw.CursorMode) == glfw.CursorNormal return nil }) @@ -475,7 +476,7 @@ func (u *UserInterface) SetCursorVisible(visible bool) { u.setInitCursorVisible(visible) return } - _ = mainthread.Run(func() error { + _ = u.t.Run(func() error { c := glfw.CursorNormal if !visible { c = glfw.CursorHidden @@ -490,7 +491,7 @@ func (u *UserInterface) IsWindowDecorated() bool { return u.isInitWindowDecorated() } v := false - _ = mainthread.Run(func() error { + _ = u.t.Run(func() error { v = u.window.GetAttrib(glfw.Decorated) == glfw.True return nil }) @@ -508,7 +509,7 @@ func (u *UserInterface) SetWindowDecorated(decorated bool) { // TODO: Now SetAttrib doesn't exist on GLFW 3.2. Revisit later (#556). // If SetAttrib exists, the implementation would be: // - // _ = mainthread.Run(func() error { + // _ = u.t.Run(func() error { // v := glfw.False // if decorated { // v = glfw.True @@ -523,7 +524,7 @@ func (u *UserInterface) IsWindowResizable() bool { return u.isInitWindowResizable() } v := false - _ = mainthread.Run(func() error { + _ = u.t.Run(func() error { v = u.window.GetAttrib(glfw.Resizable) == glfw.True return nil }) @@ -547,7 +548,7 @@ func (u *UserInterface) DeviceScaleFactor() float64 { return devicescale.GetAt(u.initMonitor.GetPos()) } - _ = mainthread.Run(func() error { + _ = u.t.Run(func() error { m := u.currentMonitor() f = devicescale.GetAt(m.GetPos()) return nil @@ -555,7 +556,15 @@ func (u *UserInterface) DeviceScaleFactor() float64 { return f } +func init() { + // Lock the main thread. + runtime.LockOSThread() +} + func (u *UserInterface) Run(width, height int, scale float64, title string, context driver.UIContext, graphics driver.Graphics) error { + // Initialize the main thread first so the thread is available at u.run (#809). + u.t = thread.New() + ch := make(chan error) go func() { defer close(ch) @@ -575,8 +584,9 @@ func (u *UserInterface) RunWithoutMainLoop(width, height int, scale float64, tit } func (u *UserInterface) run(width, height int, scale float64, title string, context driver.UIContext, graphics driver.Graphics) error { - _ = mainthread.Run(func() error { + _ = u.t.Run(func() error { u.graphics = graphics + u.graphics.SetThread(u.t) if graphics.IsGL() { glfw.WindowHint(glfw.ContextVersionMajor, 2) @@ -676,7 +686,7 @@ func (u *UserInterface) run(width, height int, scale float64, title string, cont }) var w uintptr - _ = mainthread.Run(func() error { + _ = u.t.Run(func() error { w = u.nativeWindow() return nil }) @@ -722,7 +732,7 @@ func (u *UserInterface) updateSize(context driver.UIContext) { actualScale := 0.0 sizeChanged := false // TODO: Is it possible to reduce 'runOnMainThread' calls? - _ = mainthread.Run(func() error { + _ = u.t.Run(func() error { actualScale = u.actualScreenScale() if u.lastActualScale != actualScale { u.forceSetScreenSize(u.width, u.height, u.scale, u.isFullscreen(), u.vsync) @@ -744,7 +754,7 @@ func (u *UserInterface) updateSize(context driver.UIContext) { func (u *UserInterface) update(context driver.UIContext) error { shouldClose := false - _ = mainthread.Run(func() error { + _ = u.t.Run(func() error { shouldClose = u.window.ShouldClose() return nil }) @@ -752,7 +762,7 @@ func (u *UserInterface) update(context driver.UIContext) error { return driver.RegularTermination } - _ = mainthread.Run(func() error { + _ = u.t.Run(func() error { if u.isInitFullscreen() { u.setScreenSize(u.width, u.height, u.scale, true, u.vsync) u.setInitFullscreen(false) @@ -763,7 +773,7 @@ func (u *UserInterface) update(context driver.UIContext) error { // This call is needed for initialization. u.updateSize(context) - _ = mainthread.Run(func() error { + _ = u.t.Run(func() error { glfw.PollEvents() u.input.update(u.window, u.getScale()*u.glfwScale()) @@ -789,7 +799,7 @@ func (u *UserInterface) update(context driver.UIContext) error { } // Update the screen size when the window is resizable. - _ = mainthread.Run(func() error { + _ = u.t.Run(func() error { w, h := u.reqWidth, u.reqHeight if w != 0 || h != 0 { u.setScreenSize(w, h, u.scale, u.isFullscreen(), u.vsync) @@ -803,7 +813,7 @@ func (u *UserInterface) update(context driver.UIContext) error { func (u *UserInterface) loop(context driver.UIContext) error { defer func() { - _ = mainthread.Run(func() error { + _ = u.t.Run(func() error { glfw.Terminate() return nil }) @@ -817,7 +827,7 @@ func (u *UserInterface) loop(context driver.UIContext) error { vsync := u.vsync u.m.Unlock() - _ = mainthread.Run(func() error { + _ = u.t.Run(func() error { if !vsync { u.swapBuffers() return nil