diff --git a/input.go b/input.go index ab2f4a6..37bcb04 100644 --- a/input.go +++ b/input.go @@ -1,7 +1,7 @@ package pixel import ( - "github.com/faiface/pixel/pixelgl" + "github.com/faiface/mainthread" "github.com/go-gl/glfw/v3.2/glfw" ) @@ -23,7 +23,7 @@ func (w *Window) JustReleased(button Button) bool { // MousePosition returns the current mouse position relative to the window. func (w *Window) MousePosition() Vec { var x, y, width, height float64 - pixelgl.Do(func() { + mainthread.Call(func() { x, y = w.window.GetCursorPos() wi, hi := w.window.GetSize() width, height = float64(wi), float64(hi) @@ -187,7 +187,7 @@ const ( ) func (w *Window) initInput() { - pixelgl.Do(func() { + mainthread.Call(func() { w.window.SetMouseButtonCallback(func(_ *glfw.Window, button glfw.MouseButton, action glfw.Action, mod glfw.ModifierKey) { switch action { case glfw.Press: @@ -220,7 +220,7 @@ func (w *Window) updateInput() { w.currInp.scroll -= w.tempInp.scroll // get events (usually calls callbacks, but callbacks can be called outside too) - pixelgl.Do(func() { + mainthread.Call(func() { glfw.PollEvents() }) diff --git a/monitor.go b/monitor.go index 75f2fd2..5b003ff 100644 --- a/monitor.go +++ b/monitor.go @@ -1,7 +1,7 @@ package pixel import ( - "github.com/faiface/pixel/pixelgl" + "github.com/faiface/mainthread" "github.com/go-gl/glfw/v3.2/glfw" ) @@ -12,7 +12,7 @@ type Monitor struct { // PrimaryMonitor returns the main monitor (usually the one with the taskbar and stuff). func PrimaryMonitor() *Monitor { - monitor := pixelgl.DoVal(func() interface{} { + monitor := mainthread.CallVal(func() interface{} { return glfw.GetPrimaryMonitor() }).(*glfw.Monitor) return &Monitor{ @@ -23,7 +23,7 @@ func PrimaryMonitor() *Monitor { // Monitors returns a slice of all currently available monitors. func Monitors() []*Monitor { var monitors []*Monitor - pixelgl.Do(func() { + mainthread.Call(func() { for _, monitor := range glfw.GetMonitors() { monitors = append(monitors, &Monitor{monitor: monitor}) } @@ -33,7 +33,7 @@ func Monitors() []*Monitor { // Name returns a human-readable name of a monitor. func (m *Monitor) Name() string { - name := pixelgl.DoVal(func() interface{} { + name := mainthread.CallVal(func() interface{} { return m.monitor.GetName() }).(string) return name @@ -42,7 +42,7 @@ func (m *Monitor) Name() string { // PhysicalSize returns the size of the display area of a monitor in millimeters. func (m *Monitor) PhysicalSize() (width, height float64) { var wi, hi int - pixelgl.Do(func() { + mainthread.Call(func() { wi, hi = m.monitor.GetPhysicalSize() }) width = float64(wi) @@ -53,7 +53,7 @@ func (m *Monitor) PhysicalSize() (width, height float64) { // Position returns the position of the upper-left corner of a monitor in screen coordinates. func (m *Monitor) Position() (x, y float64) { var xi, yi int - pixelgl.Do(func() { + mainthread.Call(func() { xi, yi = m.monitor.GetPos() }) x = float64(xi) @@ -63,7 +63,7 @@ func (m *Monitor) Position() (x, y float64) { // Size returns the resolution of a monitor in pixels. func (m *Monitor) Size() (width, height float64) { - mode := pixelgl.DoVal(func() interface{} { + mode := mainthread.CallVal(func() interface{} { return m.monitor.GetVideoMode() }).(*glfw.VidMode) width = float64(mode.Width) @@ -73,7 +73,7 @@ func (m *Monitor) Size() (width, height float64) { // BitDepth returns the number of bits per color of a monitor. func (m *Monitor) BitDepth() (red, green, blue int) { - mode := pixelgl.DoVal(func() interface{} { + mode := mainthread.CallVal(func() interface{} { return m.monitor.GetVideoMode() }).(*glfw.VidMode) red = mode.RedBits @@ -84,7 +84,7 @@ func (m *Monitor) BitDepth() (red, green, blue int) { // RefreshRate returns the refresh frequency of a monitor in Hz (refreshes/second). func (m *Monitor) RefreshRate() (rate float64) { - mode := pixelgl.DoVal(func() interface{} { + mode := mainthread.CallVal(func() interface{} { return m.monitor.GetVideoMode() }).(*glfw.VidMode) rate = float64(mode.RefreshRate) diff --git a/picture.go b/picture.go index 3b29071..fa6eb30 100644 --- a/picture.go +++ b/picture.go @@ -4,6 +4,7 @@ import ( "image" "image/draw" + "github.com/faiface/mainthread" "github.com/faiface/pixel/pixelgl" ) @@ -29,7 +30,7 @@ func NewPicture(img image.Image, smooth bool) *Picture { } var texture *pixelgl.Texture - pixelgl.Do(func() { + mainthread.Call(func() { texture = pixelgl.NewTexture( img.Bounds().Dx(), img.Bounds().Dy(), diff --git a/pixelgl/frame.go b/pixelgl/frame.go new file mode 100644 index 0000000..e8d663c --- /dev/null +++ b/pixelgl/frame.go @@ -0,0 +1,67 @@ +package pixelgl + +import ( + "fmt" + "runtime" + + "github.com/faiface/mainthread" + "github.com/go-gl/gl/v3.3-core/gl" +) + +type Frame struct { + fb binder + tex *Texture + width, height int +} + +func NewFrame(width, height int, smooth bool) *Frame { + f := &Frame{ + fb: binder{ + restoreLoc: gl.FRAMEBUFFER_BINDING, + bindFunc: func(obj uint32) { + gl.BindFramebuffer(gl.FRAMEBUFFER, obj) + }, + }, + width: width, + height: height, + } + + gl.GenFramebuffers(1, &f.fb.obj) + fmt.Println(f.fb.obj) + + f.tex = NewTexture(width, height, smooth, make([]uint8, width*height*4)) + + f.fb.bind() + gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, f.tex.tex.obj, 0) + f.fb.restore() + + runtime.SetFinalizer(f, (*Frame).delete) + + return f +} + +func (f *Frame) delete() { + mainthread.CallNonBlock(func() { + gl.DeleteFramebuffers(1, &f.fb.obj) + }) +} + +func (f *Frame) Width() int { + return f.width +} + +func (f *Frame) Height() int { + return f.height +} + +func (f *Frame) Begin() { + f.fb.bind() +} + +func (f *Frame) End() { + f.fb.restore() +} + +func (f *Frame) Texture() *Texture { + return f.tex +} diff --git a/pixelgl/orphan.go b/pixelgl/orphan.go index c90f1e4..111c261 100644 --- a/pixelgl/orphan.go +++ b/pixelgl/orphan.go @@ -2,6 +2,20 @@ package pixelgl import "github.com/go-gl/gl/v3.3-core/gl" +// Init initializes OpenGL by loading the function pointers from the active OpenGL context. +// This function must be manually run inside the main 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) + } + gl.Enable(gl.BLEND) + gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) +} + // Clear clears the current framebuffer or window with the given color. func Clear(r, g, b, a float32) { gl.ClearColor(r, g, b, a) diff --git a/pixelgl/shader.go b/pixelgl/shader.go index 6842925..ac592d0 100644 --- a/pixelgl/shader.go +++ b/pixelgl/shader.go @@ -4,6 +4,7 @@ import ( "fmt" "runtime" + "github.com/faiface/mainthread" "github.com/go-gl/gl/v3.3-core/gl" "github.com/go-gl/mathgl/mgl32" ) @@ -110,7 +111,7 @@ func NewShader(vertexFmt, uniformFmt AttrFormat, vertexShader, fragmentShader st } func (s *Shader) delete() { - DoNoBlock(func() { + mainthread.CallNonBlock(func() { gl.DeleteProgram(s.program.obj) }) } diff --git a/pixelgl/texture.go b/pixelgl/texture.go index ee1c22c..24eb6a9 100644 --- a/pixelgl/texture.go +++ b/pixelgl/texture.go @@ -3,6 +3,7 @@ package pixelgl import ( "runtime" + "github.com/faiface/mainthread" "github.com/go-gl/gl/v3.3-core/gl" "github.com/go-gl/mathgl/mgl32" ) @@ -67,7 +68,7 @@ func NewTexture(width, height int, smooth bool, pixels []uint8) *Texture { } func (t *Texture) delete() { - DoNoBlock(func() { + mainthread.CallNonBlock(func() { gl.DeleteTextures(1, &t.tex.obj) }) } diff --git a/pixelgl/thread.go b/pixelgl/thread.go deleted file mode 100644 index 0fefbee..0000000 --- a/pixelgl/thread.go +++ /dev/null @@ -1,102 +0,0 @@ -package pixelgl - -import ( - "runtime" - - "github.com/go-gl/gl/v3.3-core/gl" -) - -// Due to the limitations of OpenGL and operating systems, all OpenGL related calls must be -// done from the main thread. - -var ( - callQueue = make(chan func(), 8) - respChan = make(chan interface{}, 4) -) - -func init() { - runtime.LockOSThread() -} - -// Run is essentialy the "main" function of the pixelgl package. Run this function from the -// main function (because that's guaranteed to run in the main thread). -// -// This function reserves the main thread for the OpenGL stuff and runs a supplied run function -// in a separate goroutine. -// -// Run returns when the provided run function finishes. -func Run(run func()) { - done := make(chan struct{}) - - go func() { - run() - close(done) - }() - -loop: - for { - select { - case f := <-callQueue: - f() - case <-done: - break loop - } - } -} - -// Init initializes OpenGL by loading the function pointers from the active OpenGL context. -// This function must be manually run inside the main 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) - } - gl.Enable(gl.BLEND) - gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) -} - -// DoNoBlock executes a function inside the main OpenGL thread. DoNoBlock does not wait until -// the function finishes. -func DoNoBlock(f func()) { - callQueue <- f -} - -// Do executes a function inside the main OpenGL thread. Do blocks until the function finishes. -// -// All OpenGL calls must be done in the dedicated thread. -func Do(f func()) { - callQueue <- func() { - f() - respChan <- struct{}{} - } - <-respChan -} - -// DoErr executes a function inside the main 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 { - callQueue <- func() { - respChan <- f() - } - err := <-respChan - if err != nil { - return err.(error) - } - return nil -} - -// DoVal executes a function inside the main OpenGL thread and returns a value to the caller. -// DoVal blocks until the function finishes. -// -// All OpenGL calls must be done in the main thread. -func DoVal(f func() interface{}) interface{} { - callQueue <- func() { - respChan <- f() - } - return <-respChan -} diff --git a/pixelgl/vertex.go b/pixelgl/vertex.go index ea9fc5a..e7b75b6 100644 --- a/pixelgl/vertex.go +++ b/pixelgl/vertex.go @@ -4,6 +4,7 @@ import ( "fmt" "runtime" + "github.com/faiface/mainthread" "github.com/go-gl/gl/v3.3-core/gl" "github.com/pkg/errors" ) @@ -252,7 +253,7 @@ func newVertexArray(shader *Shader, cap int) *vertexArray { } func (va *vertexArray) delete() { - DoNoBlock(func() { + mainthread.CallNonBlock(func() { gl.DeleteVertexArrays(1, &va.vao.obj) gl.DeleteBuffers(1, &va.vbo.obj) }) diff --git a/run.go b/run.go index ee5f493..1dada5c 100644 --- a/run.go +++ b/run.go @@ -1,7 +1,7 @@ package pixel import ( - "github.com/faiface/pixel/pixelgl" + "github.com/faiface/mainthread" "github.com/go-gl/glfw/v3.2/glfw" ) @@ -28,5 +28,5 @@ import ( // function. func Run(run func()) { defer glfw.Terminate() - pixelgl.Run(run) + mainthread.Run(run) } diff --git a/window.go b/window.go index c6e1e82..1b6ec66 100644 --- a/window.go +++ b/window.go @@ -5,6 +5,7 @@ import ( "runtime" + "github.com/faiface/mainthread" "github.com/faiface/pixel/pixelgl" "github.com/go-gl/glfw/v3.2/glfw" "github.com/go-gl/mathgl/mgl32" @@ -79,6 +80,9 @@ type Window struct { buttons [KeyLast + 1]bool scroll Vec } + + //DEBUG + Frame *pixelgl.Frame } var currentWindow *Window @@ -94,7 +98,7 @@ func NewWindow(config WindowConfig) (*Window, error) { w := &Window{config: config} - err := pixelgl.DoErr(func() error { + err := mainthread.CallErr(func() error { err := glfw.Init() if err != nil { return err @@ -127,7 +131,7 @@ func NewWindow(config WindowConfig) (*Window, error) { return nil, errors.Wrap(err, "creating window failed") } - pixelgl.Do(func() { + mainthread.Call(func() { w.begin() w.end() @@ -160,25 +164,24 @@ func NewWindow(config WindowConfig) (*Window, error) { // Destroy destroys a window. The window can't be used any further. func (w *Window) Destroy() { - pixelgl.Do(func() { + mainthread.Call(func() { w.window.Destroy() }) } // Clear clears the window with a color. func (w *Window) Clear(c color.Color) { - pixelgl.DoNoBlock(func() { + mainthread.CallNonBlock(func() { w.begin() - defer w.end() - c := NRGBAModel.Convert(c).(NRGBA) pixelgl.Clear(float32(c.R), float32(c.G), float32(c.B), float32(c.A)) + w.end() }) } // Update swaps buffers and polls events. func (w *Window) Update() { - pixelgl.Do(func() { + mainthread.Call(func() { w.begin() if w.config.VSync { glfw.SwapInterval(1) @@ -197,7 +200,7 @@ func (w *Window) Update() { // This is usefull when overriding the user's attempt to close a window, or just to close a // window from within a program. func (w *Window) SetClosed(closed bool) { - pixelgl.Do(func() { + mainthread.Call(func() { w.window.SetShouldClose(closed) }) } @@ -206,14 +209,14 @@ func (w *Window) SetClosed(closed bool) { // // The closed flag is automatically set when a user attempts to close a window. func (w *Window) Closed() bool { - return pixelgl.DoVal(func() interface{} { + return mainthread.CallVal(func() interface{} { return w.window.ShouldClose() }).(bool) } // SetTitle changes the title of a window. func (w *Window) SetTitle(title string) { - pixelgl.Do(func() { + mainthread.Call(func() { w.window.SetTitle(title) }) } @@ -221,14 +224,14 @@ func (w *Window) SetTitle(title string) { // SetSize resizes a window to the specified size in pixels. In case of a fullscreen window, // it changes the resolution of that window. func (w *Window) SetSize(width, height float64) { - pixelgl.Do(func() { + mainthread.Call(func() { w.window.SetSize(int(width), int(height)) }) } // Size returns the size of the client area of a window (the part you can draw on). func (w *Window) Size() (width, height float64) { - pixelgl.Do(func() { + mainthread.Call(func() { wi, hi := w.window.GetSize() width = float64(wi) height = float64(hi) @@ -238,14 +241,14 @@ func (w *Window) Size() (width, height float64) { // Show makes a window visible if it was hidden. func (w *Window) Show() { - pixelgl.Do(func() { + mainthread.Call(func() { w.window.Show() }) } // Hide hides a window if it was visible. func (w *Window) Hide() { - pixelgl.Do(func() { + mainthread.Call(func() { w.window.Hide() }) } @@ -259,7 +262,7 @@ func (w *Window) Hide() { func (w *Window) SetFullscreen(monitor *Monitor) { if w.Monitor() != monitor { if monitor == nil { - pixelgl.Do(func() { + mainthread.Call(func() { w.window.SetMonitor( nil, w.restore.xpos, @@ -270,7 +273,7 @@ func (w *Window) SetFullscreen(monitor *Monitor) { ) }) } else { - pixelgl.Do(func() { + mainthread.Call(func() { w.restore.xpos, w.restore.ypos = w.window.GetPos() w.restore.width, w.restore.height = w.window.GetSize() @@ -297,7 +300,7 @@ func (w *Window) IsFullscreen() bool { // Monitor returns a monitor a fullscreen window is on. If the window is not fullscreen, this // function returns nil. func (w *Window) Monitor() *Monitor { - monitor := pixelgl.DoVal(func() interface{} { + monitor := mainthread.CallVal(func() interface{} { return w.window.GetMonitor() }).(*glfw.Monitor) if monitor == nil { @@ -310,28 +313,28 @@ func (w *Window) Monitor() *Monitor { // Focus brings a window to the front and sets input focus. func (w *Window) Focus() { - pixelgl.Do(func() { + mainthread.Call(func() { w.window.Focus() }) } // Focused returns true if a window has input focus. func (w *Window) Focused() bool { - return pixelgl.DoVal(func() interface{} { + return mainthread.CallVal(func() interface{} { return w.window.GetAttrib(glfw.Focused) == glfw.True }).(bool) } // Maximize puts a windowed window to a maximized state. func (w *Window) Maximize() { - pixelgl.Do(func() { + mainthread.Call(func() { w.window.Maximize() }) } // Restore restores a windowed window from a maximized state. func (w *Window) Restore() { - pixelgl.Do(func() { + mainthread.Call(func() { w.window.Restore() }) } @@ -346,11 +349,19 @@ func (w *Window) begin() { if w.shader != nil { w.shader.Begin() } - pixelgl.Viewport(0, 0, int32(w.width), int32(w.height)) + if w.Frame != nil { + w.Frame.Begin() + pixelgl.Viewport(0, 0, int32(w.Frame.Width()), int32(w.Frame.Height())) + } else { + pixelgl.Viewport(0, 0, int32(w.width), int32(w.height)) + } } // Note: must be called inside the main thread. func (w *Window) end() { + if w.Frame != nil { + w.Frame.End() + } if w.shader != nil { w.shader.End() } @@ -373,7 +384,7 @@ func (wt *windowTriangles) Draw() { col := wt.w.col bnd := wt.w.bnd - pixelgl.DoNoBlock(func() { + mainthread.CallNonBlock(func() { wt.w.begin() wt.w.shader.SetUniformAttr(transformMat3, mat) @@ -438,7 +449,7 @@ func (wt *windowTriangles) updateData(offset int, t Triangles) { func (wt *windowTriangles) submitData() { data := wt.data // avoid race condition - pixelgl.DoNoBlock(func() { + mainthread.CallNonBlock(func() { wt.vs.Begin() dataLen := len(data) / wt.vs.Stride() if dataLen > wt.vs.Len() { @@ -603,12 +614,11 @@ void main() { vec2 boundsMin = bounds.xy; vec2 boundsMax = bounds.zw; - float tx = boundsMin.x * (1 - Texture.x) + boundsMax.x * Texture.x; - float ty = boundsMin.y * (1 - Texture.y) + boundsMax.y * Texture.y; - if (Texture == vec2(-1, -1)) { color = maskColor * Color; } else { + float tx = boundsMin.x * (1 - Texture.x) + boundsMax.x * Texture.x; + float ty = boundsMin.y * (1 - Texture.y) + boundsMax.y * Texture.y; color = maskColor * Color * texture(tex, vec2(tx, 1 - ty)); } }