thread: Rename mainthread -> thread and add struct Thread

This enables thread available not only for the main thread but also
any threads.

This is a preparation for iOS Metal, that runs drawing functions on
a particular thread.

Updates #737
This commit is contained in:
Hajime Hoshi 2019-06-06 00:05:53 +09:00
parent 9b82ec41de
commit 15a5896efd
7 changed files with 138 additions and 108 deletions

View File

@ -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)

View File

@ -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},

View File

@ -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
}

View File

@ -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
})

View File

@ -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.
}

View File

@ -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)
}

View File

@ -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