package pixelgl import ( "errors" "runtime" "github.com/go-gl/gl/v3.3-core/gl" ) // Due to the existance and usage of thread-local variables by OpenGL, it's recommended to // execute all OpenGL calls from a single dedicated thread. This file defines functions to make // it possible. var callQueue = make(chan func(), 32) func init() { go func() { runtime.LockOSThread() for f := range callQueue { f() } }() } // Init initializes OpenGL by loading the function pointers from the active OpenGL context. // This function must be manually run inside the dedicated thread (Do, DoErr, DoVal, etc.). // // It must be called under the presence of an active OpenGL context, e.g., always after calling window.MakeContextCurrent(). // Also, always call this function when switching contexts. func Init() { err := gl.Init() if err != nil { panic(err) } } // DoNoBlock executes a function inside a dedicated OpenGL thread. // DoNoBlock does not wait until the function finishes. func DoNoBlock(f func()) { callQueue <- f } // Do executes a function inside a dedicated OpenGL thread. // Do blocks until the function finishes. // // All OpenGL calls must be done in the dedicated thread. func Do(f func()) { done := make(chan bool) callQueue <- func() { f() done <- true } <-done } // DoErr executes a function inside a dedicated OpenGL thread and returns an error to the called. // DoErr blocks until the function finishes. // // All OpenGL calls must be done in the dedicated thread. func DoErr(f func() error) error { err := make(chan error) callQueue <- func() { err <- f() } return <-err } // DoVal executes a function inside a dedicated OpenGL thread and returns a value to the caller. // DoVal blocks until the function finishes. // // All OpenGL calls must be done in the dedicated thread. func DoVal(f func() interface{}) interface{} { val := make(chan interface{}) callQueue <- func() { val <- f() } return <-val } // DoGLErr is same as Do, but also return an error generated by OpenGL. func DoGLErr(f func()) (gl error) { glerr := make(chan error) callQueue <- func() { getLastGLErr() // swallow f() glerr <- getLastGLErr() } return <-glerr } // DoErrGLErr is same as DoErr, but also returns an error generated by OpenGL. func DoErrGLErr(f func() error) (_, gl error) { err := make(chan error) glerr := make(chan error) callQueue <- func() { getLastGLErr() // swallow err <- f() glerr <- getLastGLErr() } return <-err, <-glerr } // DoValGLErr is same as DoVal, but also returns an error generated by OpenGL. func DoValGLErr(f func() interface{}) (_ interface{}, gl error) { val := make(chan interface{}) glerr := make(chan error) callQueue <- func() { getLastGLErr() // swallow val <- f() glerr <- getLastGLErr() } return <-val, <-glerr } // getLastGLErr returns (and consumes) the last error generated by OpenGL. // Don't use outside DoGLErr, DoErrGLErr and DoValGLErr. func getLastGLErr() error { err := uint32(gl.NO_ERROR) for e := gl.GetError(); e != gl.NO_ERROR; e = gl.GetError() { err = e } if err == gl.NO_ERROR { return nil } switch err { case gl.INVALID_ENUM: return errors.New("invalid enum") case gl.INVALID_VALUE: return errors.New("invalid value") case gl.INVALID_OPERATION: return errors.New("invalid operation") case gl.STACK_OVERFLOW: return errors.New("stack overflow") case gl.STACK_UNDERFLOW: return errors.New("stack underflow") case gl.OUT_OF_MEMORY: return errors.New("out of memory") case gl.INVALID_FRAMEBUFFER_OPERATION: return errors.New("invalid framebuffer operation") case gl.CONTEXT_LOST: return errors.New("context lost") default: return errors.New("unknown error") } }