From 4541fe2c8bc2e9b6cb7178976a5d159e77a7a1d3 Mon Sep 17 00:00:00 2001
From: faiface <faiface@localhost.localdomain>
Date: Thu, 5 Jan 2017 00:19:45 +0100
Subject: [PATCH] introduce new Target/Triangles/Drawer abstraction (very
 cool!)

---
 graphics.go  | 469 +++++++++++++--------------------------------------
 interface.go |  75 ++++++--
 window.go    | 223 ++++++++++++++++++++----
 3 files changed, 367 insertions(+), 400 deletions(-)

diff --git a/graphics.go b/graphics.go
index 132e470..d8b4c5d 100644
--- a/graphics.go
+++ b/graphics.go
@@ -1,416 +1,183 @@
 package pixel
 
 import (
+	"fmt"
 	"image/color"
-	"math"
-
-	"github.com/faiface/pixel/pixelgl"
-	"github.com/go-gl/mathgl/mgl32"
-	"github.com/pkg/errors"
 )
 
-// Shape is a general drawable shape constructed from vertices.
-//
-// Vertices are specified in the vertex array of a shape. A shape can have a picture, a color
-// (mask) and a static transform.
-//
-// Usually you use this type only indirectly throught other specific shapes (sprites, polygons,
-// ...) embedding it.
-type Shape struct {
-	picture   *Picture
-	color     color.Color
-	transform Transform
-	vertices  []map[pixelgl.Attr]interface{}
-	vas       map[Target]*pixelgl.VertexArray
-}
-
-// NewShape creates a new shape with specified parent, picture, color, transform and vertex array.
-func NewShape(picture *Picture, c color.Color, transform Transform, vertices []map[pixelgl.Attr]interface{}) *Shape {
-	return &Shape{
-		picture:   picture,
-		color:     c,
-		transform: transform,
-		vertices:  vertices,
-		vas:       make(map[Target]*pixelgl.VertexArray),
-	}
-}
-
-// SetPicture changes the picture of a shape.
-func (s *Shape) SetPicture(picture *Picture) {
-	s.picture = picture
-}
-
-// Picture returns the current picture of a shape.
-func (s *Shape) Picture() *Picture {
-	return s.picture
+// TrianglesData specifies a list of Triangles vertices with three common properties: Position,
+// Color and Texture.
+type TrianglesData []struct {
+	Position Vec
+	Color    color.Color
+	Texture  Vec
 }
 
-// SetColor changes the color (mask) of a shape.
-func (s *Shape) SetColor(c color.Color) {
-	s.color = c
+// Len returns the number of vertices in TrianglesData.
+func (td *TrianglesData) Len() int {
+	return len(*td)
 }
 
-// Color returns the current color (mask) of a shape.
-func (s *Shape) Color() color.Color {
-	return s.color
+// Draw is unimplemented for TrianglesData and panics.
+func (td *TrianglesData) Draw() {
+	panic(fmt.Errorf("%T.Draw: invalid operation", td))
 }
 
-// SetTransform changes the ("static") transform of a shape.
-func (s *Shape) SetTransform(transform Transform) {
-	s.transform = transform
-}
-
-// Transform returns the current ("static") transform of a shape.
-func (s *Shape) Transform() Transform {
-	return s.transform
-}
-
-// Vertices returns the vertex attribute values of all vertices in a shape.
+// Update copies vertex properties from the supplied Triangles into this TrianglesData.
 //
-// Do not change!
-func (s *Shape) Vertices() []map[pixelgl.Attr]interface{} {
-	return s.vertices
-}
-
-// Draw draws a sprite transformed by the supplied transforms applied in the reverse order.
-func (s *Shape) Draw(target Target, t ...Transform) {
-	mat := mgl32.Ident3()
-	for i := range t {
-		mat = mat.Mul3(t[i].Mat())
+// TrianglesPosition, TrianglesColor and TrianglesTexture are supported.
+func (td *TrianglesData) Update(t Triangles) {
+	if t.Len() > td.Len() {
+		*td = append(*td, make(TrianglesData, t.Len()-td.Len())...)
 	}
-	mat = mat.Mul3(s.transform.Mat())
-
-	c := NRGBAModel.Convert(s.color).(NRGBA)
-
-	if s.vas[target] == nil {
-		s.vas[target] = target.MakeVertexArray(s.vertices)
+	if t.Len() < td.Len() {
+		*td = (*td)[:t.Len()]
 	}
-	va := s.vas[target]
-
-	pixelgl.Do(func() {
-		target.Begin()
-		defer target.End()
 
-		target.Shader().SetUniformAttr(maskColorVec4, mgl32.Vec4{float32(c.R), float32(c.G), float32(c.B), float32(c.A)})
-		target.Shader().SetUniformAttr(transformMat3, mat)
+	// fast path optimization
+	if t, ok := t.(*TrianglesData); ok {
+		copy(*td, *t)
+		return
+	}
+	if t, ok := t.(*TrianglesColorData); ok {
+		copy(*td, *(*TrianglesData)(t))
+		return
+	}
+	if t, ok := t.(*TrianglesTextureData); ok {
+		copy(*td, *(*TrianglesData)(t))
+		return
+	}
 
-		if s.picture != nil {
-			s.picture.Texture().Begin()
-			va.Begin()
-			va.Draw()
-			va.End()
-			s.picture.Texture().End()
-		} else {
-			va.Begin()
-			va.Draw()
-			va.End()
+	// slow path manual copy
+	if t, ok := t.(TrianglesPosition); ok {
+		for i := range *td {
+			(*td)[i].Position = t.Position(i)
 		}
-	})
-}
-
-// MultiShape is a shape composed of several other shapes. These shapes cannot be modifies
-// after combined into a multishape.
-//
-// Using a multishape can greatly increase drawing performance. However, it's only usable when
-// the relative transformations of the shapes don't change (e.g. static blocks in a level).
-//
-// All shapes in a multishape must share the same texture (or use no texture).
-type MultiShape struct {
-	*Shape
-}
-
-// NewMultiShape creates a new multishape from several other shapes.
-//
-// If two of the supplied shapes have different pictures, this function panics.
-func NewMultiShape(shapes ...*Shape) *MultiShape {
-	var picture *Picture
-	for _, shape := range shapes {
-		if picture != nil && shape.Picture() != nil && shape.Picture().Texture().ID() != picture.Texture().ID() {
-			panic(errors.New("failed to create multishape: shapes have different pictures"))
-		}
-		if shape.Picture() != nil {
-			picture = shape.Picture()
+	}
+	if t, ok := t.(TrianglesColor); ok {
+		for i := range *td {
+			(*td)[i].Color = t.Color(i)
 		}
 	}
-
-	var vertices []map[pixelgl.Attr]interface{}
-	for _, shape := range shapes {
-		shapeVertices := shape.Vertices()
-
-		for vertex := range shapeVertices {
-			if pos, ok := shapeVertices[vertex][positionVec2]; ok {
-				pos := pos.(mgl32.Vec2)
-				pos = shape.Transform().Mat().Mul3x1(mgl32.Vec3{pos.X(), pos.Y(), 1}).Vec2()
-				shapeVertices[vertex][positionVec2] = pos
-			}
-			if color, ok := shapeVertices[vertex][colorVec4]; ok {
-				color := color.(mgl32.Vec4)
-				c := NRGBAModel.Convert(shape.Color()).(NRGBA)
-				color = mgl32.Vec4{
-					color[0] * float32(c.R),
-					color[1] * float32(c.G),
-					color[2] * float32(c.B),
-					color[3] * float32(c.A),
-				}
-				shapeVertices[vertex][colorVec4] = color
-			}
+	if t, ok := t.(TrianglesTexture); ok {
+		for i := range *td {
+			(*td)[i].Texture = t.Texture(i)
 		}
-
-		vertices = append(vertices, shapeVertices...)
 	}
-
-	return &MultiShape{NewShape(picture, color.White, Position(0), vertices)}
 }
 
-// Sprite is a picture that can be drawn on the screen. Optionally it can be color masked
-// or tranformed.
-//
-// Usually, you only transform objects when you're drawing them (by passing transforms to the
-// Draw method).  With sprites however, it can be useful to also transform them "statically". For
-// example, sprites are anchor by their bottom-left corner by default. Setting a transform can
-// change this anchored to the center, or wherever you want.
-type Sprite struct {
-	*Shape
+// Position returns the position property of i-th vertex.
+func (td *TrianglesData) Position(i int) Vec {
+	return (*td)[i].Position
 }
 
-// NewSprite creates a new sprite with the supplied picture. The sprite's size is the size of
-// the supplied picture.  If you want to change the sprite's size, change it's transform.
-func NewSprite(picture *Picture) *Sprite {
-	w, h := picture.Bounds().Size.XY()
-
-	vertices := make([]map[pixelgl.Attr]interface{}, 4)
-	for i, p := range []Vec{V(0, 0), V(w, 0), V(w, h), V(0, h)} {
-		texCoord := V(
-			(picture.Bounds().X()+p.X())/float64(picture.Texture().Width()),
-			(picture.Bounds().Y()+p.Y())/float64(picture.Texture().Height()),
-		)
-
-		vertices[i] = map[pixelgl.Attr]interface{}{
-			positionVec2: mgl32.Vec2{float32(p.X()), float32(p.Y())},
-			colorVec4:    mgl32.Vec4{1, 1, 1, 1},
-			texCoordVec2: mgl32.Vec2{float32(texCoord.X()), float32(texCoord.Y())},
-		}
-	}
-
-	vertices = []map[pixelgl.Attr]interface{}{
-		vertices[0],
-		vertices[1],
-		vertices[2],
-		vertices[0],
-		vertices[2],
-		vertices[3],
-	}
-
-	return &Sprite{NewShape(picture, color.White, Position(0), vertices)}
+// Color returns the color property of i-th vertex.
+func (td *TrianglesData) Color(i int) color.Color {
+	return (*td)[i].Color
 }
 
-// LineColor a line shape (with sharp ends) filled with a single color.
-type LineColor struct {
-	*Shape
-	a, b  Vec
-	width float64
+// Texture returns the texture property of i-th vertex.
+func (td *TrianglesData) Texture(i int) Vec {
+	return (*td)[i].Texture
 }
 
-// NewLineColor creates a new line shape between points A and B filled with a single color.
-func NewLineColor(c color.Color, a, b Vec, width float64) *LineColor {
-	vertices := make([]map[pixelgl.Attr]interface{}, 4)
-	for i := 0; i < 4; i++ {
-		vertices[i] = map[pixelgl.Attr]interface{}{
-			colorVec4:    mgl32.Vec4{1, 1, 1, 1},
-			texCoordVec2: mgl32.Vec2{-1, -1},
-		}
-	}
+// TrianglesColorData is same as TrianglesData, except is lacks Texture property.
+type TrianglesColorData TrianglesData
 
-	vertices = []map[pixelgl.Attr]interface{}{
-		vertices[0],
-		vertices[1],
-		vertices[2],
-		vertices[1],
-		vertices[2],
-		vertices[3],
-	}
-
-	lc := &LineColor{NewShape(nil, c, Position(0), vertices), a, b, width}
-	lc.setPoints()
-	return lc
+// Len returns the number of vertices in TrianglesColorData.
+func (td *TrianglesColorData) Len() int {
+	return (*TrianglesData)(td).Len()
 }
 
-// setPoints updates the vertex array data according to A, B and width.
-func (lc *LineColor) setPoints() {
-	r := (lc.b - lc.a).Unit().Scaled(lc.width / 2).Rotated(math.Pi / 2)
-	for i, p := range []Vec{lc.a - r, lc.a + r, lc.b - r, lc.b + r} {
-		lc.va.SetVertexAttr(i, positionVec2, mgl32.Vec2{float32(p.X()), float32(p.Y())})
-	}
+// Draw is unimplemented for TrianglesColorData and panics.
+func (td *TrianglesColorData) Draw() {
+	(*TrianglesData)(td).Draw()
 }
 
-// SetA changes the position of the first endpoint of a line.
-func (lc *LineColor) SetA(a Vec) {
-	lc.a = a
-	lc.setPoints()
+// Update copies vertex properties from the supplied Triangles into this TrianglesColorData.
+func (td *TrianglesColorData) Update(t Triangles) {
+	(*TrianglesData)(td).Update(t)
 }
 
-// A returns the current position of the first endpoint of a line.
-func (lc *LineColor) A() Vec {
-	return lc.a
+// Position returns the position property of i-th vertex.
+func (td *TrianglesColorData) Position(i int) Vec {
+	return (*TrianglesData)(td).Position(i)
 }
 
-// SetB changes the position of the second endpoint of a line.
-func (lc *LineColor) SetB(b Vec) {
-	lc.b = b
-	lc.setPoints()
+// Color returns the color property of i-th vertex.
+func (td *TrianglesColorData) Color(i int) color.Color {
+	return (*TrianglesData)(td).Color(i)
 }
 
-// B returns the current position of the second endpoint of a line.
-func (lc *LineColor) B() Vec {
-	return lc.b
-}
-
-// SetWidth changes the width of a line.
-func (lc *LineColor) SetWidth(width float64) {
-	lc.width = width
-	lc.setPoints()
-}
+// TrianglesTextureData is same as TrianglesData, except is lacks Color property.
+type TrianglesTextureData TrianglesData
 
-// Width returns the current width of a line.
-func (lc *LineColor) Width() float64 {
-	return lc.width
+// Len returns the number of vertices in TrianglesTextureData.
+func (td *TrianglesTextureData) Len() int {
+	return (*TrianglesData)(td).Len()
 }
 
-// PolygonColor is a convex polygon shape filled with a single color.
-type PolygonColor struct {
-	*Shape
-	points []Vec
+// Draw is unimplemented for TrianglesTextureData and panics.
+func (td *TrianglesTextureData) Draw() {
+	(*TrianglesData)(td).Draw()
 }
 
-// NewPolygonColor creates a new polygon shape filled with a single color. Parent is an object
-// that this shape belongs to, such as a window, or a graphics effect.
-func NewPolygonColor(parent pixelgl.Doer, c color.Color, points ...Vec) *PolygonColor {
-	var va *pixelgl.VertexArray
-
-	var indices []int
-	for i := 2; i < len(points); i++ {
-		indices = append(indices, 0, i-1, i)
-	}
-
-	parent.Do(func(ctx pixelgl.Context) {
-		var err error
-		va, err = pixelgl.NewVertexArray(
-			pixelgl.ContextHolder{Context: ctx},
-			ctx.Shader().VertexFormat(),
-			len(points),
-			indices,
-		)
-		if err != nil {
-			panic(errors.Wrap(err, "failed to create polygon"))
-		}
-	})
-
-	vertices := make([]map[pixelgl.Attr]interface{}, len(points))
-
-	for i, p := range points {
-		vertices[i] = map[pixelgl.Attr]interface{}{
-			positionVec2: mgl32.Vec2{float32(p.X()), float32(p.Y())},
-			colorVec4:    mgl32.Vec4{1, 1, 1, 1},
-			texCoordVec2: mgl32.Vec2{-1, -1},
-		}
-	}
-
-	va.SetVertices(vertices)
-
-	return &PolygonColor{NewShape(parent, nil, c, Position(0), va), points}
+// Update copies vertex properties from the supplied Triangles into this TrianglesTextureData.
+func (td *TrianglesTextureData) Update(t Triangles) {
+	(*TrianglesData)(td).Update(t)
 }
 
-// NumPoints returns the number of points in a polygon.
-func (pc *PolygonColor) NumPoints() int {
-	return len(pc.points)
+// Position returns the position property of i-th vertex.
+func (td *TrianglesTextureData) Position(i int) Vec {
+	return (*TrianglesData)(td).Position(i)
 }
 
-// SetPoint changes the position of a point in a polygon.
-//
-// If the index is out of range, this function panics.
-func (pc *PolygonColor) SetPoint(i int, point Vec) {
-	pc.points[i] = point
-	pc.va.SetVertexAttr(i, positionVec2, mgl32.Vec2{float32(point.X()), float32(point.Y())})
+// Texture returns the texture property of i-th vertex.
+func (td *TrianglesTextureData) Texture(i int) Vec {
+	return (*TrianglesData)(td).Texture(i)
 }
 
-// Point returns the position of a point in a polygon.
+// TrianglesDrawer is a helper type that wraps Triangles and turns them into a Drawer.
 //
-// If the index is out of range, this function panics.
-func (pc *PolygonColor) Point(i int) Vec {
-	return pc.points[i]
-}
+// It does so by creating a separate Triangles instance for each Target. The instances are
+// correctly updated alongside the wrapped Triangles.
+type TrianglesDrawer struct {
+	Triangles
 
-// EllipseColor is an ellipse shape filled with a single color.
-type EllipseColor struct {
-	*Shape
-	radius Vec
-	fill   float64
+	tris  map[Target]Triangles
+	dirty bool
 }
 
-// NewEllipseColor creates a new ellipse shape filled with a single color. Parent is an object
-// that this shape belongs to, such as a window, or a graphics effect. Fill should be a number
-// between 0 and 1 which specifies how much of the ellipse will be filled (from the outside). The
-// value of 1 means that the whole ellipse is filled. The value of 0 means that none of the
-// ellipse is filled (which makes the ellipse invisible).
-func NewEllipseColor(parent pixelgl.Doer, c color.Color, radius Vec, fill float64) *EllipseColor {
-	var va *pixelgl.VertexArray
-
-	const n = 256
-
-	var indices []int
-	for i := 2; i < (n+1)*2; i++ {
-		indices = append(indices, i-2, i-1, i)
+func (td *TrianglesDrawer) flush() {
+	if !td.dirty {
+		return
 	}
+	td.dirty = false
 
-	parent.Do(func(ctx pixelgl.Context) {
-		var err error
-		va, err = pixelgl.NewVertexArray(
-			pixelgl.ContextHolder{Context: ctx},
-			ctx.Shader().VertexFormat(),
-			(n+1)*2,
-			indices,
-		)
-		if err != nil {
-			panic(errors.Wrap(err, "failed to create ellipse"))
-		}
-	})
-
-	vertices := make([]map[pixelgl.Attr]interface{}, (n+1)*2)
-
-	for k := 0; k < n+1; k++ {
-		i, j := k*2, k*2+1
-		angle := math.Pi * 2 * float64(k%n) / n
-
-		vertices[i] = map[pixelgl.Attr]interface{}{
-			positionVec2: mgl32.Vec2{
-				float32(math.Cos(angle) * radius.X()),
-				float32(math.Sin(angle) * radius.Y()),
-			},
-			colorVec4:    mgl32.Vec4{1, 1, 1, 1},
-			texCoordVec2: mgl32.Vec2{-1, -1},
-		}
-
-		vertices[j] = map[pixelgl.Attr]interface{}{
-			positionVec2: mgl32.Vec2{
-				float32(math.Cos(angle) * radius.X() * (1 - fill)),
-				float32(math.Sin(angle) * radius.Y() * (1 - fill)),
-			},
-			colorVec4:    mgl32.Vec4{1, 1, 1, 1},
-			texCoordVec2: mgl32.Vec2{-1, -1},
-		}
+	for _, t := range td.tris {
+		t.Update(td.Triangles)
 	}
+}
 
-	va.SetVertices(vertices)
+// Draw draws the wrapped Triangles onto the provided Target.
+func (td *TrianglesDrawer) Draw(target Target) {
+	if td.tris == nil {
+		td.tris = make(map[Target]Triangles)
+	}
 
-	return &EllipseColor{NewShape(parent, nil, c, Position(0), va), radius, fill}
-}
+	td.flush()
 
-// Radius returns the radius of an ellipse.
-func (ec *EllipseColor) Radius() Vec {
-	return ec.radius
+	tri := td.tris[target]
+	if tri == nil {
+		tri = target.MakeTriangles(td.Triangles)
+		td.tris[target] = tri
+	}
+	tri.Draw()
 }
 
-// Fill returns the fill ratio of an ellipse.
-func (ec *EllipseColor) Fill() float64 {
-	return ec.fill
+// Update updates the wrapped Triangles with the supplied Triangles. Call only this method to
+// update the wrapped Triangles, otherwise the TrianglesDrawer will not work correctly.
+func (td *TrianglesDrawer) Update(t Triangles) {
+	td.dirty = true
+	td.Triangles.Update(t)
 }
diff --git a/interface.go b/interface.go
index 6bfd693..07a91d0 100644
--- a/interface.go
+++ b/interface.go
@@ -1,21 +1,72 @@
 package pixel
 
-import "github.com/faiface/pixel/pixelgl"
+import "image/color"
 
-// Target is an OpenGL graphics destination such as a window, a canvas, and so on. Something that
-// you can draw on.
+// Target is something that can be drawn onto, such as a window, a canvas, and so on.
+//
+// You can notice, that there are no "drawing" methods in a Target. That's because all drawing
+// happens indirectly through Triangles instance generated via MakeTriangles method.
 type Target interface {
-	pixelgl.BeginEnder
-	Shader() *pixelgl.Shader
+	// MakeTriangles generates a specialized copy of the provided Triangles.
+	//
+	// When calling Draw method on the returned Triangles, the Triangles will be drawn onto the
+	// target that generated them.
+	//
+	// Note, that not every Target has to recognize all possible types of triangles. Some may
+	// only recognize TrianglesPosition and TrianglesColor and ignore all other properties (if
+	// present) when making new Triangles. This varies from Target to Target.
+	MakeTriangles(Triangles) Triangles
 
-	// MakeVertexArray returns a new vertex array drawable on the Target.
-	MakeVertexArray(vertices []map[pixelgl.Attr]interface{}) *pixelgl.VertexArray
+	// These are the most basic Target "adjustment" methods.
+	SetTransform(...Transform)
+	SetMaskColor(color.Color)
+	SetPicture(*Picture)
 }
 
-// Drawer is anything that can be drawn. It's by no means a drawer inside your table.
-//
-// Drawer consists of a single methods: Draw. Draw takes a target and any number of transform
-// arguments. It's up to a Drawer to make sure that it draws correctly onto the provided target.
+// Triangles represents a list of vertices, where each three vertices form a triangle. (First,
+// second and third is the first triangle, fourth, fifth and sixth is the second triangle, etc.)
+type Triangles interface {
+	// Len returns the number of vertices. The number of triangles is the number of vertices
+	// divided by 3.
+	Len() int
+
+	// Draw draws Triangles onto an associated Target (if any).
+	//
+	// Note, that this method does not have to be implemented, however it is always implemented
+	// for Triangles generated by a Target.
+	Draw()
+
+	// Update copies vertex properties from the supplied Triangles into this Triangles.
+	//
+	// Properies not supported by these Triangles should be ignored. Properties not supported by
+	// the supplied Triangles should be left untouched.
+	//
+	// If these Triangles and supplied Triangles have different lengths, these Triangles should
+	// be resized.
+	Update(Triangles)
+}
+
+// Drawer is something that can be drawn onto any Target.
 type Drawer interface {
-	Draw(target Target, t ...Transform)
+	Draw(Target)
+}
+
+// TrianglesPosition specifies Triangles with Position property.
+type TrianglesPosition interface {
+	Triangles
+	Position(i int) Vec
+}
+
+// TrianglesColor specifies Triangles with Color property.
+type TrianglesColor interface {
+	Triangles
+	Color(i int) color.Color
+}
+
+// TrianglesTexture specifies Triangles with Texture propery.
+//
+// Note that this represents texture coordinates, not an actual texture.
+type TrianglesTexture interface {
+	Triangles
+	Texture(i int) Vec
 }
diff --git a/window.go b/window.go
index 9879eec..9700456 100644
--- a/window.go
+++ b/window.go
@@ -61,6 +61,7 @@ type Window struct {
 	window  *glfw.Window
 	config  WindowConfig
 	shader  *pixelgl.Shader
+	pic     *Picture
 
 	// need to save these to correctly restore a fullscreen window
 	restore struct {
@@ -120,8 +121,8 @@ func NewWindow(config WindowConfig) (*Window, error) {
 	}
 
 	pixelgl.Do(func() {
-		w.Begin()
-		defer w.End()
+		w.begin()
+		defer w.end()
 
 		w.shader, err = pixelgl.NewShader(
 			defaultVertexFormat,
@@ -129,6 +130,9 @@ func NewWindow(config WindowConfig) (*Window, error) {
 			defaultVertexShader,
 			defaultFragmentShader,
 		)
+		if err != nil {
+			panic(errors.Wrap(err, "NewWindow: failed to create shader"))
+		}
 
 		// default uniforms
 		w.shader.Begin()
@@ -161,8 +165,8 @@ func (w *Window) Destroy() {
 // Clear clears the window with a color.
 func (w *Window) Clear(c color.Color) {
 	pixelgl.DoNoBlock(func() {
-		w.Begin()
-		defer w.End()
+		w.begin()
+		defer w.end()
 
 		c := NRGBAModel.Convert(c).(NRGBA)
 		gl.ClearColor(float32(c.R), float32(c.G), float32(c.B), float32(c.A))
@@ -173,8 +177,8 @@ func (w *Window) Clear(c color.Color) {
 // Update swaps buffers and polls events.
 func (w *Window) Update() {
 	pixelgl.Do(func() {
-		w.Begin()
-		defer w.End()
+		w.begin()
+		defer w.end()
 
 		if w.config.VSync {
 			glfw.SwapInterval(1)
@@ -185,8 +189,8 @@ func (w *Window) Update() {
 	w.updateInput()
 
 	pixelgl.Do(func() {
-		w.Begin()
-		defer w.End()
+		w.begin()
+		defer w.end()
 
 		w, h := w.window.GetSize()
 		gl.Viewport(0, 0, int32(w), int32(h))
@@ -342,13 +346,10 @@ func (w *Window) Restore() {
 	})
 }
 
-// Begin makes the OpenGL context of a window current and binds it's shader.
-//
-// You usually do not need to use this method, however, you have to use it when you're
-// directly using this window's context (such as drawing on it using OpenGL).
+// begin makes the OpenGL context of a window current and binds it's shader.
 //
 // Note, that this method must be called inside the main OpenGL thread (pixelgl.Do/DoNoBlock/DoErr/DoVal).
-func (w *Window) Begin() {
+func (w *Window) begin() {
 	if currentWindow != w {
 		w.window.MakeContextCurrent()
 		pixelgl.Init()
@@ -359,39 +360,187 @@ func (w *Window) Begin() {
 	}
 }
 
-// End unbinds the shader of a window.
-func (w *Window) End() {
+// end unbinds the shader of a window.
+//
+// Note, that this method must be called inside the main OpenGL thread (pixelgl.Do/DoNoBlock/DoErr/DoVal).
+func (w *Window) end() {
 	if w.shader != nil {
 		w.shader.End()
 	}
 }
 
-// MakeVertexArray implements Target.
-func (w *Window) MakeVertexArray(vertices []map[pixelgl.Attr]interface{}) *pixelgl.VertexArray {
-	var va *pixelgl.VertexArray
+type windowTrianglesData []struct {
+	position Vec
+	color    NRGBA
+	texture  Vec
+}
+
+type windowTriangles struct {
+	w        *Window
+	va       *pixelgl.VertexArray
+	data     windowTrianglesData
+	attrData []map[pixelgl.Attr]interface{}
+	dirty    bool
+}
+
+func (wt *windowTriangles) flush() {
+	if !wt.dirty {
+		return
+	}
+	wt.dirty = false
+
+	if wt.va == nil || wt.va.NumVertices() != wt.Len() {
+		// reallocate vertex array
+		pixelgl.Do(func() {
+			var err error
+			wt.va, err = pixelgl.NewVertexArray(wt.w.shader, wt.Len())
+			if err != nil {
+				panic(errors.Wrap(err, "windowTriangles: failed to create vertex array"))
+			}
+		})
+	}
+
+	if wt.Len() > len(wt.attrData) {
+		wt.attrData = append(wt.attrData, make([]map[pixelgl.Attr]interface{}, wt.Len()-len(wt.attrData))...)
+	}
+	if wt.Len() < len(wt.attrData) {
+		wt.attrData = wt.attrData[:wt.Len()]
+	}
+
+	for i, v := range wt.data {
+		if wt.attrData[i] == nil {
+			wt.attrData[i] = make(map[pixelgl.Attr]interface{})
+		}
+		wt.attrData[i][positionVec2] = mgl32.Vec2{float32(v.position.X()), float32(v.position.Y())}
+		wt.attrData[i][colorVec4] = mgl32.Vec4{float32(v.color.R), float32(v.color.G), float32(v.color.B), float32(v.color.A)}
+		wt.attrData[i][textureVec2] = mgl32.Vec2{float32(v.texture.X()), float32(v.texture.Y())}
+	}
 
 	pixelgl.Do(func() {
-		w.Begin()
-		defer w.End()
+		wt.va.Begin()
+		wt.va.SetVertices(wt.attrData)
+		wt.va.End()
+	})
+}
 
-		var err error
-		va, err = pixelgl.NewVertexArray(w.Shader(), len(vertices))
-		if err != nil {
-			panic(err)
+func (wt *windowTriangles) Len() int {
+	return len(wt.data)
+}
+
+func (wt *windowTriangles) Draw() {
+	wt.flush()
+	pixelgl.DoNoBlock(func() {
+		wt.w.begin()
+		if wt.w.pic != nil {
+			wt.w.pic.Texture().Begin()
 		}
+		wt.va.Begin()
+		wt.va.Draw()
+		wt.va.End()
+		if wt.w.pic != nil {
+			wt.w.pic.Texture().End()
+		}
+		wt.w.end()
+	})
+}
+
+func (wt *windowTriangles) Update(t Triangles) {
+	wt.dirty = true
 
-		va.Begin()
-		va.SetVertices(vertices)
-		va.End()
+	if t.Len() > wt.Len() {
+		newData := make(windowTrianglesData, t.Len())
+		// default attribute values
+		for i := range newData {
+			newData[i].color = NRGBA{R: 1, G: 1, B: 1, A: 1}
+			newData[i].texture = V(-1, -1)
+		}
+		wt.data = append(wt.data, newData...)
+	}
+	if t.Len() < wt.Len() {
+		wt.data = wt.data[:t.Len()]
+	}
+
+	if t, ok := t.(TrianglesPosition); ok {
+		for i := range wt.data {
+			wt.data[i].position = t.Position(i)
+		}
+	}
+	if t, ok := t.(TrianglesColor); ok {
+		for i := range wt.data {
+			wt.data[i].color = NRGBAModel.Convert(t.Color(i)).(NRGBA)
+		}
+	}
+	if t, ok := t.(TrianglesTexture); ok {
+		for i := range wt.data {
+			wt.data[i].texture = t.Texture(i)
+		}
+	}
+}
+
+func (wt *windowTriangles) Position(i int) Vec {
+	return wt.data[i].position
+}
+
+func (wt *windowTriangles) Color(i int) color.Color {
+	return wt.data[i].color
+}
+
+func (wt *windowTriangles) Texture(i int) Vec {
+	return wt.data[i].texture
+}
+
+// MakeTriangles generates a specialized copy of the supplied triangles that will draw onto this
+// Window.
+//
+// Window supports TrianglesPosition, TrianglesColor and TrianglesTexture.
+func (w *Window) MakeTriangles(t Triangles) Triangles {
+	wt := &windowTriangles{
+		w: w,
+	}
+	wt.Update(t)
+	return wt
+}
+
+// SetTransform sets a global transformation matrix for the Window.
+//
+// Transforms are applied right-to-left.
+func (w *Window) SetTransform(t ...Transform) {
+	mat := mgl32.Ident3()
+	for i := range t {
+		mat = mat.Mul3(t[i].Mat())
+	}
+
+	pixelgl.DoNoBlock(func() {
+		w.begin()
+		w.shader.SetUniformAttr(transformMat3, mat)
+		w.end()
 	})
+}
+
+// SetMaskColor sets a global mask color for the Window.
+func (w *Window) SetMaskColor(c color.Color) {
+	nrgba := NRGBAModel.Convert(c).(NRGBA)
+	r := float32(nrgba.R)
+	g := float32(nrgba.G)
+	b := float32(nrgba.B)
+	a := float32(nrgba.A)
+
+	pixelgl.DoNoBlock(func() {
+		w.begin()
+		w.shader.SetUniformAttr(maskColorVec4, mgl32.Vec4{r, g, b, a})
+		w.end()
+	})
+}
 
-	return va
+// SetPicture sets a Picture that will be used in subsequent drawings onto the window.
+func (w *Window) SetPicture(p *Picture) {
+	w.pic = p
 }
 
 var defaultVertexFormat = pixelgl.AttrFormat{
 	"position": pixelgl.Vec2,
 	"color":    pixelgl.Vec4,
-	"texCoord": pixelgl.Vec2,
+	"texture":  pixelgl.Vec2,
 }
 
 var defaultUniformFormat = pixelgl.AttrFormat{
@@ -404,17 +553,17 @@ var defaultVertexShader = `
 
 in vec2 position;
 in vec4 color;
-in vec2 texCoord;
+in vec2 texture;
 
 out vec4 Color;
-out vec2 TexCoord;
+out vec2 Texture;
 
 uniform mat3 transform;
 
 void main() {
 	gl_Position = vec4((transform * vec3(position.x, position.y, 1.0)).xy, 0.0, 1.0);
 	Color = color;
-	TexCoord = texCoord;
+	Texture = texture;
 }
 `
 
@@ -422,7 +571,7 @@ var defaultFragmentShader = `
 #version 330 core
 
 in vec4 Color;
-in vec2 TexCoord;
+in vec2 Texture;
 
 out vec4 color;
 
@@ -430,10 +579,10 @@ uniform vec4 maskColor;
 uniform sampler2D tex;
 
 void main() {
-	if (TexCoord == vec2(-1, -1)) {
+	if (Texture == vec2(-1, -1)) {
 		color = maskColor * Color;
 	} else {
-		color = maskColor * Color * texture(tex, vec2(TexCoord.x, 1 - TexCoord.y));
+		color = maskColor * Color * texture(tex, vec2(Texture.x, 1 - Texture.y));
 	}
 }
 `
@@ -447,8 +596,8 @@ var (
 		Name: "color",
 		Type: pixelgl.Vec4,
 	}
-	texCoordVec2 = pixelgl.Attr{
-		Name: "texCoord",
+	textureVec2 = pixelgl.Attr{
+		Name: "texture",
 		Type: pixelgl.Vec2,
 	}
 	maskColorVec4 = pixelgl.Attr{
-- 
GitLab