├── examples └── demo │ ├── celebrate.png │ └── main.go ├── go.mod ├── interface.go ├── doc.go ├── util.go ├── LICENSE ├── go.sum ├── attr.go ├── orphan.go ├── frame.go ├── texture.go ├── README.md ├── shader.go └── vertex.go /examples/demo/celebrate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faiface/glhf/HEAD/examples/demo/celebrate.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/faiface/glhf 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 7 | github.com/go-gl/gl v0.0.0-20210905235341-f7a045908259 8 | github.com/go-gl/glfw v0.0.0-20210727001814-0db043d8d5be 9 | github.com/go-gl/mathgl v1.0.0 10 | github.com/pkg/errors v0.9.1 11 | ) 12 | -------------------------------------------------------------------------------- /interface.go: -------------------------------------------------------------------------------- 1 | package glhf 2 | 3 | // BeginEnder is an interface for manipulating OpenGL state. 4 | // 5 | // OpenGL is a state machine. Every object can 'enter' it's state and 'leave' it's state. For 6 | // example, you can bind a buffer and unbind a buffer, bind a texture and unbind it, use shader 7 | // and unuse it, and so on. 8 | type BeginEnder interface { 9 | Begin() 10 | End() 11 | } 12 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package glhf provides abstractions around the basic OpenGL primitives and operations. 2 | // 3 | // All calls should be done from the main thread using "github.com/faiface/mainthread" package. 4 | // 5 | // This package deliberately does not handle nor report trivial OpenGL errors, it's up to you to 6 | // cause none. It does of course report errors like shader compilation error and such. 7 | package glhf 8 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package glhf 2 | 3 | import "github.com/go-gl/gl/v3.3-core/gl" 4 | 5 | type binder struct { 6 | restoreLoc uint32 7 | bindFunc func(uint32) 8 | 9 | obj uint32 10 | 11 | prev []uint32 12 | } 13 | 14 | func (b *binder) bind() *binder { 15 | var prev int32 16 | gl.GetIntegerv(b.restoreLoc, &prev) 17 | b.prev = append(b.prev, uint32(prev)) 18 | 19 | if b.prev[len(b.prev)-1] != b.obj { 20 | b.bindFunc(b.obj) 21 | } 22 | return b 23 | } 24 | 25 | func (b *binder) restore() *binder { 26 | if b.prev[len(b.prev)-1] != b.obj { 27 | b.bindFunc(b.prev[len(b.prev)-1]) 28 | } 29 | b.prev = b.prev[:len(b.prev)-1] 30 | return b 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Michal Štrba 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 h1:baVdMKlASEHrj19iqjARrPbaRisD7EuZEVJj6ZMLl1Q= 2 | github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3/go.mod h1:VEPNJUlxl5KdWjDvz6Q1l+rJlxF2i6xqDeGuGAxa87M= 3 | github.com/go-gl/gl v0.0.0-20210905235341-f7a045908259 h1:8q7+xl2D2qHPLTII1t4vSMNP2VKwDcn+Avf2WXvdB1A= 4 | github.com/go-gl/gl v0.0.0-20210905235341-f7a045908259/go.mod h1:wjpnOv6ONl2SuJSxqCPVaPZibGFdSci9HFocT9qtVYM= 5 | github.com/go-gl/glfw v0.0.0-20210727001814-0db043d8d5be h1:UVW91pfMB1GRQfVwC7//RGVbqX6Ea8jURmJhlANak1M= 6 | github.com/go-gl/glfw v0.0.0-20210727001814-0db043d8d5be/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 7 | github.com/go-gl/mathgl v1.0.0 h1:t9DznWJlXxxjeeKLIdovCOVJQk/GzDEL7h/h+Ro2B68= 8 | github.com/go-gl/mathgl v1.0.0/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ= 9 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 10 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 11 | golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f h1:FO4MZ3N56GnxbqxGKqh+YTzUWQ2sDwtFQEZgLOxh9Jc= 12 | golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 13 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 14 | -------------------------------------------------------------------------------- /attr.go: -------------------------------------------------------------------------------- 1 | package glhf 2 | 3 | // AttrFormat defines names and types of OpenGL attributes (vertex format, uniform format, etc.). 4 | // 5 | // Example: 6 | // AttrFormat{{"position", Vec2}, {"color", Vec4}, {"texCoord": Vec2}} 7 | type AttrFormat []Attr 8 | 9 | // Size returns the total size of all attributes of the AttrFormat. 10 | func (af AttrFormat) Size() int { 11 | total := 0 12 | for _, attr := range af { 13 | total += attr.Type.Size() 14 | } 15 | return total 16 | } 17 | 18 | // Attr represents an arbitrary OpenGL attribute, such as a vertex attribute or a shader 19 | // uniform attribute. 20 | type Attr struct { 21 | Name string 22 | Type AttrType 23 | } 24 | 25 | // AttrType represents the type of an OpenGL attribute. 26 | type AttrType int 27 | 28 | // List of all possible attribute types. 29 | const ( 30 | Int AttrType = iota 31 | Float 32 | Vec2 33 | Vec3 34 | Vec4 35 | Mat2 36 | Mat23 37 | Mat24 38 | Mat3 39 | Mat32 40 | Mat34 41 | Mat4 42 | Mat42 43 | Mat43 44 | ) 45 | 46 | // Size returns the size of a type in bytes. 47 | func (at AttrType) Size() int { 48 | switch at { 49 | case Int: 50 | return 4 51 | case Float: 52 | return 4 53 | case Vec2: 54 | return 2 * 4 55 | case Vec3: 56 | return 3 * 4 57 | case Vec4: 58 | return 4 * 4 59 | case Mat2: 60 | return 2 * 2 * 4 61 | case Mat23: 62 | return 2 * 3 * 4 63 | case Mat24: 64 | return 2 * 4 * 4 65 | case Mat3: 66 | return 3 * 3 * 4 67 | case Mat32: 68 | return 3 * 2 * 4 69 | case Mat34: 70 | return 3 * 4 * 4 71 | case Mat4: 72 | return 4 * 4 * 4 73 | case Mat42: 74 | return 4 * 2 * 4 75 | case Mat43: 76 | return 4 * 3 * 4 77 | default: 78 | panic("size of vertex attribute type: invalid type") 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /orphan.go: -------------------------------------------------------------------------------- 1 | package glhf 2 | 3 | import "github.com/go-gl/gl/v3.3-core/gl" 4 | 5 | // Init initializes OpenGL by loading function pointers from the active OpenGL context. 6 | // This function must be manually run inside the main thread (using "github.com/faiface/mainthread" 7 | // package). 8 | // 9 | // It must be called under the presence of an active OpenGL context, e.g., always after calling 10 | // window.MakeContextCurrent(). Also, always call this function when switching contexts. 11 | func Init() { 12 | err := gl.Init() 13 | if err != nil { 14 | panic(err) 15 | } 16 | gl.Enable(gl.BLEND) 17 | gl.Enable(gl.SCISSOR_TEST) 18 | gl.BlendEquation(gl.FUNC_ADD) 19 | } 20 | 21 | // Clear clears the current framebuffer or window with the given color. 22 | func Clear(r, g, b, a float32) { 23 | gl.ClearColor(r, g, b, a) 24 | gl.Clear(gl.COLOR_BUFFER_BIT) 25 | } 26 | 27 | // Bounds sets the drawing bounds in pixels. Drawing outside bounds is always discarted. 28 | // 29 | // Calling this function is equivalent to setting viewport and scissor in OpenGL. 30 | func Bounds(x, y, w, h int) { 31 | gl.Viewport(int32(x), int32(y), int32(w), int32(h)) 32 | gl.Scissor(int32(x), int32(y), int32(w), int32(h)) 33 | } 34 | 35 | // BlendFactor represents a source or destination blend factor. 36 | type BlendFactor int 37 | 38 | // Here's the list of all blend factors. 39 | const ( 40 | One = BlendFactor(gl.ONE) 41 | Zero = BlendFactor(gl.ZERO) 42 | SrcAlpha = BlendFactor(gl.SRC_ALPHA) 43 | DstAlpha = BlendFactor(gl.DST_ALPHA) 44 | OneMinusSrcAlpha = BlendFactor(gl.ONE_MINUS_SRC_ALPHA) 45 | OneMinusDstAlpha = BlendFactor(gl.ONE_MINUS_DST_ALPHA) 46 | ) 47 | 48 | // BlendFunc sets the source and destination blend factor. 49 | func BlendFunc(src, dst BlendFactor) { 50 | gl.BlendFunc(uint32(src), uint32(dst)) 51 | } 52 | -------------------------------------------------------------------------------- /frame.go: -------------------------------------------------------------------------------- 1 | package glhf 2 | 3 | import ( 4 | "runtime" 5 | 6 | "github.com/faiface/mainthread" 7 | "github.com/go-gl/gl/v3.3-core/gl" 8 | ) 9 | 10 | // Frame is a fixed resolution texture that you can draw on. 11 | type Frame struct { 12 | fb, rf, df binder // framebuffer, read framebuffer, draw framebuffer 13 | tex *Texture 14 | } 15 | 16 | // NewFrame creates a new fully transparent Frame with given dimensions in pixels. 17 | func NewFrame(width, height int, smooth bool) *Frame { 18 | f := &Frame{ 19 | fb: binder{ 20 | restoreLoc: gl.FRAMEBUFFER_BINDING, 21 | bindFunc: func(obj uint32) { 22 | gl.BindFramebuffer(gl.FRAMEBUFFER, obj) 23 | }, 24 | }, 25 | rf: binder{ 26 | restoreLoc: gl.READ_FRAMEBUFFER_BINDING, 27 | bindFunc: func(obj uint32) { 28 | gl.BindFramebuffer(gl.READ_FRAMEBUFFER, obj) 29 | }, 30 | }, 31 | df: binder{ 32 | restoreLoc: gl.DRAW_FRAMEBUFFER_BINDING, 33 | bindFunc: func(obj uint32) { 34 | gl.BindFramebuffer(gl.DRAW_FRAMEBUFFER, obj) 35 | }, 36 | }, 37 | tex: NewTexture(width, height, smooth, make([]uint8, width*height*4)), 38 | } 39 | 40 | gl.GenFramebuffers(1, &f.fb.obj) 41 | 42 | f.fb.bind() 43 | gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, f.tex.tex.obj, 0) 44 | f.fb.restore() 45 | 46 | runtime.SetFinalizer(f, (*Frame).delete) 47 | 48 | return f 49 | } 50 | 51 | func (f *Frame) delete() { 52 | mainthread.CallNonBlock(func() { 53 | gl.DeleteFramebuffers(1, &f.fb.obj) 54 | }) 55 | } 56 | 57 | // ID returns the OpenGL framebuffer ID of this Frame. 58 | func (f *Frame) ID() uint32 { 59 | return f.fb.obj 60 | } 61 | 62 | // Begin binds the Frame. All draw operations will target this Frame until End is called. 63 | func (f *Frame) Begin() { 64 | f.fb.bind() 65 | } 66 | 67 | // End unbinds the Frame. All draw operations will go to whatever was bound before this Frame. 68 | func (f *Frame) End() { 69 | f.fb.restore() 70 | } 71 | 72 | // Blit copies rectangle (sx0, sy0, sx1, sy1) in this Frame onto rectangle (dx0, dy0, dx1, dy1) in 73 | // dst Frame. 74 | // 75 | // If the dst Frame is nil, the destination will be the framebuffer 0, which is the screen. 76 | // 77 | // If the sizes of the rectangles don't match, the source will be stretched to fit the destination 78 | // rectangle. The stretch will be either smooth or pixely according to the source Frame's 79 | // smoothness. 80 | func (f *Frame) Blit(dst *Frame, sx0, sy0, sx1, sy1, dx0, dy0, dx1, dy1 int) { 81 | f.rf.obj = f.fb.obj 82 | if dst != nil { 83 | f.df.obj = dst.fb.obj 84 | } else { 85 | f.df.obj = 0 86 | } 87 | f.rf.bind() 88 | f.df.bind() 89 | 90 | filter := gl.NEAREST 91 | if f.tex.smooth { 92 | filter = gl.LINEAR 93 | } 94 | 95 | gl.BlitFramebuffer( 96 | int32(sx0), int32(sy0), int32(sx1), int32(sy1), 97 | int32(dx0), int32(dy0), int32(dx1), int32(dy1), 98 | gl.COLOR_BUFFER_BIT, uint32(filter), 99 | ) 100 | 101 | f.rf.restore() 102 | f.df.restore() 103 | } 104 | 105 | // Texture returns the Frame's underlying Texture that the Frame draws on. 106 | func (f *Frame) Texture() *Texture { 107 | return f.tex 108 | } 109 | -------------------------------------------------------------------------------- /texture.go: -------------------------------------------------------------------------------- 1 | package glhf 2 | 3 | import ( 4 | "runtime" 5 | 6 | "github.com/faiface/mainthread" 7 | "github.com/go-gl/gl/v3.3-core/gl" 8 | "github.com/go-gl/mathgl/mgl32" 9 | ) 10 | 11 | // Texture is an OpenGL texture. 12 | type Texture struct { 13 | tex binder 14 | width, height int 15 | smooth bool 16 | } 17 | 18 | // NewTexture creates a new texture with the specified width and height with some initial 19 | // pixel values. The pixels must be a sequence of RGBA values (one byte per component). 20 | func NewTexture(width, height int, smooth bool, pixels []uint8) *Texture { 21 | tex := &Texture{ 22 | tex: binder{ 23 | restoreLoc: gl.TEXTURE_BINDING_2D, 24 | bindFunc: func(obj uint32) { 25 | gl.BindTexture(gl.TEXTURE_2D, obj) 26 | }, 27 | }, 28 | width: width, 29 | height: height, 30 | } 31 | 32 | gl.GenTextures(1, &tex.tex.obj) 33 | 34 | tex.Begin() 35 | defer tex.End() 36 | 37 | // initial data 38 | gl.TexImage2D( 39 | gl.TEXTURE_2D, 40 | 0, 41 | gl.RGBA, 42 | int32(width), 43 | int32(height), 44 | 0, 45 | gl.RGBA, 46 | gl.UNSIGNED_BYTE, 47 | gl.Ptr(pixels), 48 | ) 49 | 50 | borderColor := mgl32.Vec4{0, 0, 0, 0} 51 | gl.TexParameterfv(gl.TEXTURE_2D, gl.TEXTURE_BORDER_COLOR, &borderColor[0]) 52 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_BORDER) 53 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_BORDER) 54 | 55 | tex.SetSmooth(smooth) 56 | 57 | runtime.SetFinalizer(tex, (*Texture).delete) 58 | 59 | return tex 60 | } 61 | 62 | func (t *Texture) delete() { 63 | mainthread.CallNonBlock(func() { 64 | gl.DeleteTextures(1, &t.tex.obj) 65 | }) 66 | } 67 | 68 | // ID returns the OpenGL ID of this Texture. 69 | func (t *Texture) ID() uint32 { 70 | return t.tex.obj 71 | } 72 | 73 | // Width returns the width of the Texture in pixels. 74 | func (t *Texture) Width() int { 75 | return t.width 76 | } 77 | 78 | // Height returns the height of the Texture in pixels. 79 | func (t *Texture) Height() int { 80 | return t.height 81 | } 82 | 83 | // SetPixels sets the content of a sub-region of the Texture. Pixels must be an RGBA byte sequence. 84 | func (t *Texture) SetPixels(x, y, w, h int, pixels []uint8) { 85 | if len(pixels) != w*h*4 { 86 | panic("set pixels: wrong number of pixels") 87 | } 88 | gl.TexSubImage2D( 89 | gl.TEXTURE_2D, 90 | 0, 91 | int32(x), 92 | int32(y), 93 | int32(w), 94 | int32(h), 95 | gl.RGBA, 96 | gl.UNSIGNED_BYTE, 97 | gl.Ptr(pixels), 98 | ) 99 | } 100 | 101 | // Pixels returns the content of a sub-region of the Texture as an RGBA byte sequence. 102 | func (t *Texture) Pixels(x, y, w, h int) []uint8 { 103 | pixels := make([]uint8, t.width*t.height*4) 104 | gl.GetTexImage( 105 | gl.TEXTURE_2D, 106 | 0, 107 | gl.RGBA, 108 | gl.UNSIGNED_BYTE, 109 | gl.Ptr(pixels), 110 | ) 111 | subPixels := make([]uint8, w*h*4) 112 | for i := 0; i < h; i++ { 113 | row := pixels[(i+y)*t.width*4+x*4 : (i+y)*t.width*4+(x+w)*4] 114 | subRow := subPixels[i*w*4 : (i+1)*w*4] 115 | copy(subRow, row) 116 | } 117 | return subPixels 118 | } 119 | 120 | // SetSmooth sets whether the Texture should be drawn "smoothly" or "pixely". 121 | // 122 | // It affects how the Texture is drawn when zoomed. Smooth interpolates between the neighbour 123 | // pixels, while pixely always chooses the nearest pixel. 124 | func (t *Texture) SetSmooth(smooth bool) { 125 | t.smooth = smooth 126 | if smooth { 127 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) 128 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) 129 | } else { 130 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) 131 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) 132 | } 133 | } 134 | 135 | // Smooth returns whether the Texture is set to be drawn "smooth" or "pixely". 136 | func (t *Texture) Smooth() bool { 137 | return t.smooth 138 | } 139 | 140 | // Begin binds the Texture. This is necessary before using the Texture. 141 | func (t *Texture) Begin() { 142 | t.tex.bind() 143 | } 144 | 145 | // End unbinds the Texture and restores the previous one. 146 | func (t *Texture) End() { 147 | t.tex.restore() 148 | } 149 | -------------------------------------------------------------------------------- /examples/demo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image" 5 | "image/draw" 6 | _ "image/png" 7 | "os" 8 | 9 | "github.com/faiface/glhf" 10 | "github.com/faiface/mainthread" 11 | "github.com/go-gl/glfw/v3.1/glfw" 12 | ) 13 | 14 | func loadImage(path string) (*image.NRGBA, error) { 15 | file, err := os.Open(path) 16 | if err != nil { 17 | return nil, err 18 | } 19 | img, _, err := image.Decode(file) 20 | if err != nil { 21 | return nil, err 22 | } 23 | bounds := img.Bounds() 24 | nrgba := image.NewNRGBA(image.Rect(0, 0, bounds.Dx(), bounds.Dy())) 25 | draw.Draw(nrgba, nrgba.Bounds(), img, bounds.Min, draw.Src) 26 | return nrgba, nil 27 | } 28 | 29 | func run() { 30 | var win *glfw.Window 31 | 32 | defer func() { 33 | mainthread.Call(func() { 34 | glfw.Terminate() 35 | }) 36 | }() 37 | 38 | mainthread.Call(func() { 39 | glfw.Init() 40 | 41 | glfw.WindowHint(glfw.ContextVersionMajor, 3) 42 | glfw.WindowHint(glfw.ContextVersionMinor, 3) 43 | glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile) 44 | glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True) 45 | glfw.WindowHint(glfw.Resizable, glfw.False) 46 | 47 | var err error 48 | 49 | win, err = glfw.CreateWindow(560, 697, "GLHF Rocks!", nil, nil) 50 | if err != nil { 51 | panic(err) 52 | } 53 | 54 | win.MakeContextCurrent() 55 | 56 | glhf.Init() 57 | }) 58 | 59 | var ( 60 | // Here we define a vertex format of our vertex slice. It's actually a basic slice 61 | // literal. 62 | // 63 | // The vertex format consists of names and types of the attributes. The name is the 64 | // name that the attribute is referenced by inside a shader. 65 | vertexFormat = glhf.AttrFormat{ 66 | {Name: "position", Type: glhf.Vec2}, 67 | {Name: "texture", Type: glhf.Vec2}, 68 | } 69 | 70 | // Here we declare some variables for later use. 71 | shader *glhf.Shader 72 | texture *glhf.Texture 73 | slice *glhf.VertexSlice 74 | ) 75 | 76 | // Here we load an image from a file. The loadImage function is not within the library, it 77 | // just loads and returns a image.NRGBA. 78 | gopherImage, err := loadImage("celebrate.png") 79 | if err != nil { 80 | panic(err) 81 | } 82 | 83 | // Every OpenGL call needs to be done inside the main thread. 84 | mainthread.Call(func() { 85 | var err error 86 | 87 | // Here we create a shader. The second argument is the format of the uniform 88 | // attributes. Since our shader has no uniform attributes, the format is empty. 89 | shader, err = glhf.NewShader(vertexFormat, glhf.AttrFormat{}, vertexShader, fragmentShader) 90 | 91 | // If the shader compilation did not go successfully, an error with a full 92 | // description is returned. 93 | if err != nil { 94 | panic(err) 95 | } 96 | 97 | // We create a texture from the loaded image. 98 | texture = glhf.NewTexture( 99 | gopherImage.Bounds().Dx(), 100 | gopherImage.Bounds().Dy(), 101 | true, 102 | gopherImage.Pix, 103 | ) 104 | 105 | // And finally, we make a vertex slice, which is basically a dynamically sized 106 | // vertex array. The length of the slice is 6 and the capacity is the same. 107 | // 108 | // The slice inherits the vertex format of the supplied shader. Also, it should 109 | // only be used with that shader. 110 | slice = glhf.MakeVertexSlice(shader, 6, 6) 111 | 112 | // Before we use a slice, we need to Begin it. The same holds for all objects in 113 | // GLHF. 114 | slice.Begin() 115 | 116 | // We assign data to the vertex slice. The values are in the order as in the vertex 117 | // format of the slice (shader). Each two floats correspond to an attribute of type 118 | // glhf.Vec2. 119 | slice.SetVertexData([]float32{ 120 | -1, -1, 0, 1, 121 | +1, -1, 1, 1, 122 | +1, +1, 1, 0, 123 | 124 | -1, -1, 0, 1, 125 | +1, +1, 1, 0, 126 | -1, +1, 0, 0, 127 | }) 128 | 129 | // When we're done with the slice, we End it. 130 | slice.End() 131 | }) 132 | 133 | shouldQuit := false 134 | for !shouldQuit { 135 | mainthread.Call(func() { 136 | if win.ShouldClose() { 137 | shouldQuit = true 138 | } 139 | 140 | // Clear the window. 141 | glhf.Clear(1, 1, 1, 1) 142 | 143 | // Here we Begin/End all necessary objects and finally draw the vertex 144 | // slice. 145 | shader.Begin() 146 | texture.Begin() 147 | slice.Begin() 148 | slice.Draw() 149 | slice.End() 150 | texture.End() 151 | shader.End() 152 | 153 | win.SwapBuffers() 154 | glfw.PollEvents() 155 | }) 156 | } 157 | } 158 | 159 | func main() { 160 | mainthread.Run(run) 161 | } 162 | 163 | var vertexShader = ` 164 | #version 330 core 165 | 166 | in vec2 position; 167 | in vec2 texture; 168 | 169 | out vec2 Texture; 170 | 171 | void main() { 172 | gl_Position = vec4(position, 0.0, 1.0); 173 | Texture = texture; 174 | } 175 | ` 176 | 177 | var fragmentShader = ` 178 | #version 330 core 179 | 180 | in vec2 Texture; 181 | 182 | out vec4 color; 183 | 184 | uniform sampler2D tex; 185 | 186 | void main() { 187 | color = texture(tex, Texture); 188 | } 189 | ` 190 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # \*\*\*\*\*NOTICE\*\*\*\*\* 2 | 3 | This repo is not under active development anymore and has been archived. Continued development has been migrated to [gopxl/glhf](https://github.com/gopxl/glhf). A big thank you to [faiface](https://github.com/faiface) for creating this awesome library and for all the hard work put into it. We encourage old and new users to check out the new repo and contribute to it. 4 | 5 | # glhf [![GoDoc](https://godoc.org/github.com/faiface/glhf?status.svg)](http://godoc.org/github.com/faiface/glhf) [![Report card](https://goreportcard.com/badge/github.com/faiface/glhf)](https://goreportcard.com/report/github.com/faiface/glhf) 6 | 7 | open**GL** **H**ave **F**un - A Go package that makes life with OpenGL enjoyable. 8 | 9 | ``` 10 | go get github.com/faiface/glhf 11 | ``` 12 | 13 | ## Main features 14 | 15 | - Garbage collected OpenGL objects 16 | - Dynamically sized vertex slices (vertex arrays are boring) 17 | - Textures, Shaders, Frames (reasonably managed framebuffers) 18 | - Always possible to use standard OpenGL with `glhf` 19 | 20 | ## Motivation 21 | 22 | OpenGL is verbose, it's usage patterns are repetitive and it's manual memory management doesn't fit 23 | Go's design. When making a game development library, it's usually desirable to create some 24 | higher-level abstractions around OpenGL. This library is a take on that. 25 | 26 | ## Contribute! 27 | 28 | The library is young and many features are still missing. If you find a bug, have a proposal or a 29 | feature request, _do an issue_!. If you know how to implement something that's missing, _do a pull 30 | request_. 31 | 32 | ## Code 33 | 34 | The following are parts of the demo program, which can be found in the [examples](https://github.com/faiface/glhf/tree/master/examples/demo). 35 | 36 | ```go 37 | // ... GLFW window creation and stuff ... 38 | 39 | // vertex shader source 40 | var vertexShader = ` 41 | #version 330 core 42 | 43 | in vec2 position; 44 | in vec2 texture; 45 | 46 | out vec2 Texture; 47 | 48 | void main() { 49 | gl_Position = vec4(position, 0.0, 1.0); 50 | Texture = texture; 51 | } 52 | ` 53 | 54 | // fragment shader source 55 | var fragmentShader = ` 56 | #version 330 core 57 | 58 | in vec2 Texture; 59 | 60 | out vec4 color; 61 | 62 | uniform sampler2D tex; 63 | 64 | void main() { 65 | color = texture(tex, Texture); 66 | } 67 | ` 68 | 69 | var ( 70 | // Here we define a vertex format of our vertex slice. It's actually a basic slice 71 | // literal. 72 | // 73 | // The vertex format consists of names and types of the attributes. The name is the 74 | // name that the attribute is referenced by inside a shader. 75 | vertexFormat = glhf.AttrFormat{ 76 | {Name: "position", Type: glhf.Vec2}, 77 | {Name: "texture", Type: glhf.Vec2}, 78 | } 79 | 80 | // Here we declare some variables for later use. 81 | shader *glhf.Shader 82 | texture *glhf.Texture 83 | slice *glhf.VertexSlice 84 | ) 85 | 86 | // Here we load an image from a file. The loadImage function is not within the library, it 87 | // just loads and returns a image.NRGBA. 88 | gopherImage, err := loadImage("celebrate.png") 89 | if err != nil { 90 | panic(err) 91 | } 92 | 93 | // Every OpenGL call needs to be done inside the main thread. 94 | mainthread.Call(func() { 95 | var err error 96 | 97 | // Here we create a shader. The second argument is the format of the uniform 98 | // attributes. Since our shader has no uniform attributes, the format is empty. 99 | shader, err = glhf.NewShader(vertexFormat, glhf.AttrFormat{}, vertexShader, fragmentShader) 100 | 101 | // If the shader compilation did not go successfully, an error with a full 102 | // description is returned. 103 | if err != nil { 104 | panic(err) 105 | } 106 | 107 | // We create a texture from the loaded image. 108 | texture = glhf.NewTexture( 109 | gopherImage.Bounds().Dx(), 110 | gopherImage.Bounds().Dy(), 111 | true, 112 | gopherImage.Pix, 113 | ) 114 | 115 | // And finally, we make a vertex slice, which is basically a dynamically sized 116 | // vertex array. The length of the slice is 6 and the capacity is the same. 117 | // 118 | // The slice inherits the vertex format of the supplied shader. Also, it should 119 | // only be used with that shader. 120 | slice = glhf.MakeVertexSlice(shader, 6, 6) 121 | 122 | // Before we use a slice, we need to Begin it. The same holds for all objects in 123 | // GLHF. 124 | slice.Begin() 125 | 126 | // We assign data to the vertex slice. The values are in the order as in the vertex 127 | // format of the slice (shader). Each two floats correspond to an attribute of type 128 | // glhf.Vec2. 129 | slice.SetVertexData([]float32{ 130 | -1, -1, 0, 1, 131 | +1, -1, 1, 1, 132 | +1, +1, 1, 0, 133 | 134 | -1, -1, 0, 1, 135 | +1, +1, 1, 0, 136 | -1, +1, 0, 0, 137 | }) 138 | 139 | // When we're done with the slice, we End it. 140 | slice.End() 141 | }) 142 | 143 | shouldQuit := false 144 | for !shouldQuit { 145 | mainthread.Call(func() { 146 | // ... GLFW stuff ... 147 | 148 | // Clear the window. 149 | glhf.Clear(1, 1, 1, 1) 150 | 151 | // Here we Begin/End all necessary objects and finally draw the vertex 152 | // slice. 153 | shader.Begin() 154 | texture.Begin() 155 | slice.Begin() 156 | slice.Draw() 157 | slice.End() 158 | texture.End() 159 | shader.End() 160 | 161 | // ... GLFW stuff ... 162 | }) 163 | } 164 | ``` 165 | 166 | ## FAQ 167 | 168 | ### Which version of OpenGL does GLHF use? 169 | 170 | It uses OpenGL 3.3 and uses 171 | [`github.com/go-gl/gl/v3.3-core/gl`](https://github.com/go-gl/gl/tree/master/v3.3-core/gl). 172 | 173 | ### Why do I have to use `github.com/faiface/mainthread` package with GLHF? 174 | 175 | First of all, OpenGL has to be done from one thread and many operating systems require, that the one 176 | thread will be the main thread of your application. 177 | 178 | But why that specific package? GLHF uses the `mainthread` package to do the garbage collection of 179 | OpenGL objects, which is super convenient. So in order for it to work correctly, you have to 180 | initialize the `mainthread` package through `mainthread.Run`. However, once you call this function 181 | there is no way to run functions on the main thread, except for through the `mainthread` package. 182 | 183 | ### Why is the important XY feature not included? 184 | 185 | I probably didn't need it yet. If you want that features, create an issue or implement it and do a 186 | pull request. 187 | 188 | ### Does GLHF create windows for me? 189 | 190 | No. You have to use another library for windowing, e.g. 191 | [github.com/go-gl/glfw/v3.2/glfw](https://github.com/go-gl/glfw/tree/master/v3.2/glfw). 192 | 193 | ### Why no tests? 194 | 195 | If you find a way to automatically test OpenGL, I may add tests. 196 | -------------------------------------------------------------------------------- /shader.go: -------------------------------------------------------------------------------- 1 | package glhf 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | 7 | "github.com/faiface/mainthread" 8 | "github.com/go-gl/gl/v3.3-core/gl" 9 | "github.com/go-gl/mathgl/mgl32" 10 | ) 11 | 12 | // Shader is an OpenGL shader program. 13 | type Shader struct { 14 | program binder 15 | vertexFmt AttrFormat 16 | uniformFmt AttrFormat 17 | uniformLoc []int32 18 | } 19 | 20 | // NewShader creates a new shader program from the specified vertex shader and fragment shader 21 | // sources. 22 | // 23 | // Note that vertexShader and fragmentShader parameters must contain the source code, they're 24 | // not filenames. 25 | func NewShader(vertexFmt, uniformFmt AttrFormat, vertexShader, fragmentShader string) (*Shader, error) { 26 | shader := &Shader{ 27 | program: binder{ 28 | restoreLoc: gl.CURRENT_PROGRAM, 29 | bindFunc: func(obj uint32) { 30 | gl.UseProgram(obj) 31 | }, 32 | }, 33 | vertexFmt: vertexFmt, 34 | uniformFmt: uniformFmt, 35 | uniformLoc: make([]int32, len(uniformFmt)), 36 | } 37 | 38 | var vshader, fshader uint32 39 | 40 | // vertex shader 41 | { 42 | vshader = gl.CreateShader(gl.VERTEX_SHADER) 43 | src, free := gl.Strs(vertexShader) 44 | defer free() 45 | length := int32(len(vertexShader)) 46 | gl.ShaderSource(vshader, 1, src, &length) 47 | gl.CompileShader(vshader) 48 | 49 | var success int32 50 | gl.GetShaderiv(vshader, gl.COMPILE_STATUS, &success) 51 | if success == gl.FALSE { 52 | var logLen int32 53 | gl.GetShaderiv(vshader, gl.INFO_LOG_LENGTH, &logLen) 54 | 55 | infoLog := make([]byte, logLen) 56 | gl.GetShaderInfoLog(vshader, logLen, nil, &infoLog[0]) 57 | return nil, fmt.Errorf("error compiling vertex shader: %s", string(infoLog)) 58 | } 59 | 60 | defer gl.DeleteShader(vshader) 61 | } 62 | 63 | // fragment shader 64 | { 65 | fshader = gl.CreateShader(gl.FRAGMENT_SHADER) 66 | src, free := gl.Strs(fragmentShader) 67 | defer free() 68 | length := int32(len(fragmentShader)) 69 | gl.ShaderSource(fshader, 1, src, &length) 70 | gl.CompileShader(fshader) 71 | 72 | var success int32 73 | gl.GetShaderiv(fshader, gl.COMPILE_STATUS, &success) 74 | if success == gl.FALSE { 75 | var logLen int32 76 | gl.GetShaderiv(fshader, gl.INFO_LOG_LENGTH, &logLen) 77 | 78 | infoLog := make([]byte, logLen) 79 | gl.GetShaderInfoLog(fshader, logLen, nil, &infoLog[0]) 80 | return nil, fmt.Errorf("error compiling fragment shader: %s", string(infoLog)) 81 | } 82 | 83 | defer gl.DeleteShader(fshader) 84 | } 85 | 86 | // shader program 87 | { 88 | shader.program.obj = gl.CreateProgram() 89 | gl.AttachShader(shader.program.obj, vshader) 90 | gl.AttachShader(shader.program.obj, fshader) 91 | gl.LinkProgram(shader.program.obj) 92 | 93 | var success int32 94 | gl.GetProgramiv(shader.program.obj, gl.LINK_STATUS, &success) 95 | if success == gl.FALSE { 96 | var logLen int32 97 | gl.GetProgramiv(shader.program.obj, gl.INFO_LOG_LENGTH, &logLen) 98 | 99 | infoLog := make([]byte, logLen) 100 | gl.GetProgramInfoLog(shader.program.obj, logLen, nil, &infoLog[0]) 101 | return nil, fmt.Errorf("error linking shader program: %s", string(infoLog)) 102 | } 103 | } 104 | 105 | // uniforms 106 | for i, uniform := range uniformFmt { 107 | loc := gl.GetUniformLocation(shader.program.obj, gl.Str(uniform.Name+"\x00")) 108 | shader.uniformLoc[i] = loc 109 | } 110 | 111 | runtime.SetFinalizer(shader, (*Shader).delete) 112 | 113 | return shader, nil 114 | } 115 | 116 | func (s *Shader) delete() { 117 | mainthread.CallNonBlock(func() { 118 | gl.DeleteProgram(s.program.obj) 119 | }) 120 | } 121 | 122 | // ID returns the OpenGL ID of this Shader. 123 | func (s *Shader) ID() uint32 { 124 | return s.program.obj 125 | } 126 | 127 | // VertexFormat returns the vertex attribute format of this Shader. Do not change it. 128 | func (s *Shader) VertexFormat() AttrFormat { 129 | return s.vertexFmt 130 | } 131 | 132 | // UniformFormat returns the uniform attribute format of this Shader. Do not change it. 133 | func (s *Shader) UniformFormat() AttrFormat { 134 | return s.uniformFmt 135 | } 136 | 137 | // SetUniformAttr sets the value of a uniform attribute of this Shader. The attribute is 138 | // specified by the index in the Shader's uniform format. 139 | // 140 | // If the uniform attribute does not exist in the Shader, this method returns false. 141 | // 142 | // Supplied value must correspond to the type of the attribute. Correct types are these 143 | // (right-hand is the type of the value): 144 | // Attr{Type: Int}: int32 145 | // Attr{Type: Float}: float32 146 | // Attr{Type: Vec2}: mgl32.Vec2 147 | // Attr{Type: Vec3}: mgl32.Vec3 148 | // Attr{Type: Vec4}: mgl32.Vec4 149 | // Attr{Type: Mat2}: mgl32.Mat2 150 | // Attr{Type: Mat23}: mgl32.Mat2x3 151 | // Attr{Type: Mat24}: mgl32.Mat2x4 152 | // Attr{Type: Mat3}: mgl32.Mat3 153 | // Attr{Type: Mat32}: mgl32.Mat3x2 154 | // Attr{Type: Mat34}: mgl32.Mat3x4 155 | // Attr{Type: Mat4}: mgl32.Mat4 156 | // Attr{Type: Mat42}: mgl32.Mat4x2 157 | // Attr{Type: Mat43}: mgl32.Mat4x3 158 | // No other types are supported. 159 | // 160 | // The Shader must be bound before calling this method. 161 | func (s *Shader) SetUniformAttr(uniform int, value interface{}) (ok bool) { 162 | if s.uniformLoc[uniform] < 0 { 163 | return false 164 | } 165 | 166 | switch s.uniformFmt[uniform].Type { 167 | case Int: 168 | value := value.(int32) 169 | gl.Uniform1iv(s.uniformLoc[uniform], 1, &value) 170 | case Float: 171 | value := value.(float32) 172 | gl.Uniform1fv(s.uniformLoc[uniform], 1, &value) 173 | case Vec2: 174 | value := value.(mgl32.Vec2) 175 | gl.Uniform2fv(s.uniformLoc[uniform], 1, &value[0]) 176 | case Vec3: 177 | value := value.(mgl32.Vec3) 178 | gl.Uniform3fv(s.uniformLoc[uniform], 1, &value[0]) 179 | case Vec4: 180 | value := value.(mgl32.Vec4) 181 | gl.Uniform4fv(s.uniformLoc[uniform], 1, &value[0]) 182 | case Mat2: 183 | value := value.(mgl32.Mat2) 184 | gl.UniformMatrix2fv(s.uniformLoc[uniform], 1, false, &value[0]) 185 | case Mat23: 186 | value := value.(mgl32.Mat2x3) 187 | gl.UniformMatrix2x3fv(s.uniformLoc[uniform], 1, false, &value[0]) 188 | case Mat24: 189 | value := value.(mgl32.Mat2x4) 190 | gl.UniformMatrix2x4fv(s.uniformLoc[uniform], 1, false, &value[0]) 191 | case Mat3: 192 | value := value.(mgl32.Mat3) 193 | gl.UniformMatrix3fv(s.uniformLoc[uniform], 1, false, &value[0]) 194 | case Mat32: 195 | value := value.(mgl32.Mat3x2) 196 | gl.UniformMatrix3x2fv(s.uniformLoc[uniform], 1, false, &value[0]) 197 | case Mat34: 198 | value := value.(mgl32.Mat3x4) 199 | gl.UniformMatrix3x4fv(s.uniformLoc[uniform], 1, false, &value[0]) 200 | case Mat4: 201 | value := value.(mgl32.Mat4) 202 | gl.UniformMatrix4fv(s.uniformLoc[uniform], 1, false, &value[0]) 203 | case Mat42: 204 | value := value.(mgl32.Mat4x2) 205 | gl.UniformMatrix4x2fv(s.uniformLoc[uniform], 1, false, &value[0]) 206 | case Mat43: 207 | value := value.(mgl32.Mat4x3) 208 | gl.UniformMatrix4x3fv(s.uniformLoc[uniform], 1, false, &value[0]) 209 | default: 210 | panic("set uniform attr: invalid attribute type") 211 | } 212 | 213 | return true 214 | } 215 | 216 | // Begin binds the Shader program. This is necessary before using the Shader. 217 | func (s *Shader) Begin() { 218 | s.program.bind() 219 | } 220 | 221 | // End unbinds the Shader program and restores the previous one. 222 | func (s *Shader) End() { 223 | s.program.restore() 224 | } 225 | -------------------------------------------------------------------------------- /vertex.go: -------------------------------------------------------------------------------- 1 | package glhf 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | 7 | "github.com/faiface/mainthread" 8 | "github.com/go-gl/gl/v3.3-core/gl" 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | // VertexSlice points to a portion of (or possibly whole) vertex array. It is used as a pointer, 13 | // contrary to Go's builtin slices. This is, so that append can be 'in-place'. That's for the good, 14 | // because Begin/End-ing a VertexSlice would become super confusing, if append returned a new 15 | // VertexSlice. 16 | // 17 | // It also implements all basic slice-like operations: appending, sub-slicing, etc. 18 | // 19 | // Note that you need to Begin a VertexSlice before getting or updating it's elements or drawing it. 20 | // After you're done with it, you need to End it. 21 | type VertexSlice struct { 22 | va *vertexArray 23 | i, j int 24 | } 25 | 26 | // MakeVertexSlice allocates a new vertex array with specified capacity and returns a VertexSlice 27 | // that points to it's first len elements. 28 | // 29 | // Note, that a vertex array is specialized for a specific shader and can't be used with another 30 | // shader. 31 | func MakeVertexSlice(shader *Shader, len, cap int) *VertexSlice { 32 | if len > cap { 33 | panic("failed to make vertex slice: len > cap") 34 | } 35 | return &VertexSlice{ 36 | va: newVertexArray(shader, cap), 37 | i: 0, 38 | j: len, 39 | } 40 | } 41 | 42 | // VertexFormat returns the format of vertex attributes inside the underlying vertex array of this 43 | // VertexSlice. 44 | func (vs *VertexSlice) VertexFormat() AttrFormat { 45 | return vs.va.format 46 | } 47 | 48 | // Stride returns the number of float32 elements occupied by one vertex. 49 | func (vs *VertexSlice) Stride() int { 50 | return vs.va.stride / 4 51 | } 52 | 53 | // Len returns the length of the VertexSlice (number of vertices). 54 | func (vs *VertexSlice) Len() int { 55 | return vs.j - vs.i 56 | } 57 | 58 | // Cap returns the capacity of an underlying vertex array. 59 | func (vs *VertexSlice) Cap() int { 60 | return vs.va.cap - vs.i 61 | } 62 | 63 | // SetLen resizes the VertexSlice to length len. 64 | func (vs *VertexSlice) SetLen(len int) { 65 | vs.End() // vs must have been Begin-ed before calling this method 66 | *vs = vs.grow(len) 67 | vs.Begin() 68 | } 69 | 70 | // grow returns supplied vs with length changed to len. Allocates new underlying vertex array if 71 | // necessary. The original content is preserved. 72 | func (vs VertexSlice) grow(len int) VertexSlice { 73 | if len <= vs.Cap() { 74 | // capacity sufficient 75 | return VertexSlice{ 76 | va: vs.va, 77 | i: vs.i, 78 | j: vs.i + len, 79 | } 80 | } 81 | 82 | // grow the capacity 83 | newCap := vs.Cap() 84 | if newCap < 1024 { 85 | newCap += newCap 86 | } else { 87 | newCap += newCap / 4 88 | } 89 | if newCap < len { 90 | newCap = len 91 | } 92 | newVs := VertexSlice{ 93 | va: newVertexArray(vs.va.shader, newCap), 94 | i: 0, 95 | j: len, 96 | } 97 | // preserve the original content 98 | newVs.Begin() 99 | newVs.Slice(0, vs.Len()).SetVertexData(vs.VertexData()) 100 | newVs.End() 101 | return newVs 102 | } 103 | 104 | // Slice returns a sub-slice of this VertexSlice covering the range [i, j) (relative to this 105 | // VertexSlice). 106 | // 107 | // Note, that the returned VertexSlice shares an underlying vertex array with the original 108 | // VertexSlice. Modifying the contents of one modifies corresponding contents of the other. 109 | func (vs *VertexSlice) Slice(i, j int) *VertexSlice { 110 | if i < 0 || j < i || j > vs.va.cap { 111 | panic("failed to slice vertex slice: index out of range") 112 | } 113 | return &VertexSlice{ 114 | va: vs.va, 115 | i: vs.i + i, 116 | j: vs.i + j, 117 | } 118 | } 119 | 120 | // SetVertexData sets the contents of the VertexSlice. 121 | // 122 | // The data is a slice of float32's, where each vertex attribute occupies a certain number of 123 | // elements. Namely, Float occupies 1, Vec2 occupies 2, Vec3 occupies 3 and Vec4 occupies 4. The 124 | // attribues in the data slice must be in the same order as in the vertex format of this Vertex 125 | // Slice. 126 | // 127 | // If the length of vertices does not match the length of the VertexSlice, this methdo panics. 128 | func (vs *VertexSlice) SetVertexData(data []float32) { 129 | if len(data)/vs.Stride() != vs.Len() { 130 | fmt.Println(len(data)/vs.Stride(), vs.Len()) 131 | panic("set vertex data: wrong length of vertices") 132 | } 133 | vs.va.setVertexData(vs.i, vs.j, data) 134 | } 135 | 136 | // VertexData returns the contents of the VertexSlice. 137 | // 138 | // The data is in the same format as with SetVertexData. 139 | func (vs *VertexSlice) VertexData() []float32 { 140 | return vs.va.vertexData(vs.i, vs.j) 141 | } 142 | 143 | // Draw draws the content of the VertexSlice. 144 | func (vs *VertexSlice) Draw() { 145 | vs.va.draw(vs.i, vs.j) 146 | } 147 | 148 | // Begin binds the underlying vertex array. Calling this method is necessary before using the VertexSlice. 149 | func (vs *VertexSlice) Begin() { 150 | vs.va.begin() 151 | } 152 | 153 | // End unbinds the underlying vertex array. Call this method when you're done with VertexSlice. 154 | func (vs *VertexSlice) End() { 155 | vs.va.end() 156 | } 157 | 158 | type vertexArray struct { 159 | vao, vbo binder 160 | cap int 161 | format AttrFormat 162 | stride int 163 | offset []int 164 | shader *Shader 165 | } 166 | 167 | const vertexArrayMinCap = 4 168 | 169 | func newVertexArray(shader *Shader, cap int) *vertexArray { 170 | if cap < vertexArrayMinCap { 171 | cap = vertexArrayMinCap 172 | } 173 | 174 | va := &vertexArray{ 175 | vao: binder{ 176 | restoreLoc: gl.VERTEX_ARRAY_BINDING, 177 | bindFunc: func(obj uint32) { 178 | gl.BindVertexArray(obj) 179 | }, 180 | }, 181 | vbo: binder{ 182 | restoreLoc: gl.ARRAY_BUFFER_BINDING, 183 | bindFunc: func(obj uint32) { 184 | gl.BindBuffer(gl.ARRAY_BUFFER, obj) 185 | }, 186 | }, 187 | cap: cap, 188 | format: shader.VertexFormat(), 189 | stride: shader.VertexFormat().Size(), 190 | offset: make([]int, len(shader.VertexFormat())), 191 | shader: shader, 192 | } 193 | 194 | offset := 0 195 | for i, attr := range va.format { 196 | switch attr.Type { 197 | case Float, Vec2, Vec3, Vec4: 198 | default: 199 | panic(errors.New("failed to create vertex array: invalid attribute type")) 200 | } 201 | va.offset[i] = offset 202 | offset += attr.Type.Size() 203 | } 204 | 205 | gl.GenVertexArrays(1, &va.vao.obj) 206 | 207 | va.vao.bind() 208 | 209 | gl.GenBuffers(1, &va.vbo.obj) 210 | defer va.vbo.bind().restore() 211 | 212 | emptyData := make([]byte, cap*va.stride) 213 | gl.BufferData(gl.ARRAY_BUFFER, len(emptyData), gl.Ptr(emptyData), gl.DYNAMIC_DRAW) 214 | 215 | for i, attr := range va.format { 216 | loc := gl.GetAttribLocation(shader.program.obj, gl.Str(attr.Name+"\x00")) 217 | 218 | var size int32 219 | switch attr.Type { 220 | case Float: 221 | size = 1 222 | case Vec2: 223 | size = 2 224 | case Vec3: 225 | size = 3 226 | case Vec4: 227 | size = 4 228 | } 229 | 230 | gl.VertexAttribPointerWithOffset( 231 | uint32(loc), 232 | size, 233 | gl.FLOAT, 234 | false, 235 | int32(va.stride), 236 | uintptr(va.offset[i]), 237 | ) 238 | gl.EnableVertexAttribArray(uint32(loc)) 239 | } 240 | 241 | va.vao.restore() 242 | 243 | runtime.SetFinalizer(va, (*vertexArray).delete) 244 | 245 | return va 246 | } 247 | 248 | func (va *vertexArray) delete() { 249 | mainthread.CallNonBlock(func() { 250 | gl.DeleteVertexArrays(1, &va.vao.obj) 251 | gl.DeleteBuffers(1, &va.vbo.obj) 252 | }) 253 | } 254 | 255 | func (va *vertexArray) begin() { 256 | va.vao.bind() 257 | va.vbo.bind() 258 | } 259 | 260 | func (va *vertexArray) end() { 261 | va.vbo.restore() 262 | va.vao.restore() 263 | } 264 | 265 | func (va *vertexArray) draw(i, j int) { 266 | gl.DrawArrays(gl.TRIANGLES, int32(i), int32(j-i)) 267 | } 268 | 269 | func (va *vertexArray) setVertexData(i, j int, data []float32) { 270 | if j-i == 0 { 271 | // avoid setting 0 bytes of buffer data 272 | return 273 | } 274 | gl.BufferSubData(gl.ARRAY_BUFFER, i*va.stride, len(data)*4, gl.Ptr(data)) 275 | } 276 | 277 | func (va *vertexArray) vertexData(i, j int) []float32 { 278 | if j-i == 0 { 279 | // avoid getting 0 bytes of buffer data 280 | return nil 281 | } 282 | data := make([]float32, (j-i)*va.stride/4) 283 | gl.GetBufferSubData(gl.ARRAY_BUFFER, i*va.stride, len(data)*4, gl.Ptr(data)) 284 | return data 285 | } 286 | --------------------------------------------------------------------------------