diff --git a/input.go b/input.go
index ab2f4a664992e1c5a78fca45cf5d1281198ba8b2..37bcb04ef2810326c02909db316c9030dd1f0892 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 75f2fd2276de03db4fcd2805597f2b7d5b59d407..5b003ff2a0ccfa92ef7a48406eb36dcab8dae8d7 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 3b29071175c6ee2a94b88aed11104fe7c9b033eb..fa6eb304a223da9c2e4917f0d6948018317a6551 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 0000000000000000000000000000000000000000..e8d663cdd96935122f3fb03ed9b4c010bb4fd1c0
--- /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 c90f1e4a1c48155354e2ed6b2fb033a28784e418..111c2614e8f1d4aafdac646018089ea977a78429 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 6842925db3f4bac0042bc6780fb117631d8f9d13..ac592d056ce974c9ece15e09aed01faf7e4c62f1 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 ee1c22c8dc77d907a5376406b32b69fd7ce4f8cf..24eb6a97a045883718ec701996048d2adff14e24 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 0fefbee9a545d1a9b2816300cdba197a897e4450..0000000000000000000000000000000000000000
--- 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 ea9fc5ae72ce4487912182715d5e3481cd02bbe0..e7b75b657fb97302968bc6118e3ad2ce416bae00 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 ee5f4938740c7b4ef64b8a41a770f00b3022b8b5..1dada5c93b46a61d48aa8273078155308bdcd19b 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 c6e1e82b2a1983e43e34d71128baeff3f4d735c0..1b6ec66099886a72d8263dc6e5a15a90970717bf 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));
 	}
 }