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