diff --git a/graphics.go b/graphics.go index 2d488a1..899bc90 100644 --- a/graphics.go +++ b/graphics.go @@ -25,13 +25,14 @@ func (td *TrianglesData) Draw() { func (td *TrianglesData) resize(len int) { if len > td.Len() { - newData := make(TrianglesData, len-td.Len()) - // default values - for i := range newData { - newData[i].Color = NRGBA{1, 1, 1, 1} - newData[i].Texture = V(-1, -1) + needAppend := len - td.Len() + for i := 0; i < needAppend; i++ { + *td = append(*td, struct { + Position Vec + Color NRGBA + Texture Vec + }{V(0, 0), NRGBA{1, 1, 1, 1}, V(-1, -1)}) } - *td = append(*td, newData...) } if len < td.Len() { *td = (*td)[:len] diff --git a/pixelgl/attr.go b/pixelgl/attr.go index 8278f54..3112ab4 100644 --- a/pixelgl/attr.go +++ b/pixelgl/attr.go @@ -3,29 +3,14 @@ package pixelgl // AttrFormat defines names and types of OpenGL attributes (vertex format, uniform format, etc.). // // Example: -// AttrFormat{"position": Vec2, "color": Vec4, "texCoord": Vec2} -type AttrFormat map[string]AttrType - -// Contains checks whether a format contains a specific attribute. -// -// It does a little more than a hard check: e.g. if you query a Vec2 attribute, but the format -// contains Vec3, Contains returns true, because Vec2 is assignable to Vec3. Specifically, -// Float -> Vec2 -> Vec3 -> Vec4 (transitively). This however does not work for matrices or ints. -func (af AttrFormat) Contains(attr Attr) bool { - if typ, ok := af[attr.Name]; ok { - if (Float <= typ && typ <= Vec4) && (Float <= attr.Type && attr.Type <= typ) { - return true - } - return attr.Type == typ - } - return false -} +// AttrFormat{{"position", Vec2}, {"color", Vec4}, {"texCoord": Vec2}} +type AttrFormat []Attr // Size returns the total size of all attributes of an attribute format. func (af AttrFormat) Size() int { total := 0 - for _, typ := range af { - total += typ.Size() + for _, attr := range af { + total += attr.Type.Size() } return total } diff --git a/pixelgl/shader.go b/pixelgl/shader.go index 61652ac..6842925 100644 --- a/pixelgl/shader.go +++ b/pixelgl/shader.go @@ -13,7 +13,7 @@ type Shader struct { program binder vertexFmt AttrFormat uniformFmt AttrFormat - uniforms map[string]int32 + uniformLoc []int32 } // NewShader creates a new shader program from the specified vertex shader and fragment shader @@ -31,7 +31,7 @@ func NewShader(vertexFmt, uniformFmt AttrFormat, vertexShader, fragmentShader st }, vertexFmt: vertexFmt, uniformFmt: uniformFmt, - uniforms: make(map[string]int32), + uniformLoc: make([]int32, len(uniformFmt)), } var vshader, fshader uint32 @@ -99,9 +99,9 @@ func NewShader(vertexFmt, uniformFmt AttrFormat, vertexShader, fragmentShader st } // uniforms - for name := range uniformFmt { - loc := gl.GetUniformLocation(shader.program.obj, gl.Str(name+"\x00")) - shader.uniforms[name] = loc + for i, uniform := range uniformFmt { + loc := gl.GetUniformLocation(shader.program.obj, gl.Str(uniform.Name+"\x00")) + shader.uniformLoc[i] = loc } runtime.SetFinalizer(shader, (*Shader).delete) @@ -125,9 +125,10 @@ func (s *Shader) UniformFormat() AttrFormat { return s.uniformFmt } -// SetUniformAttr sets the value of a uniform attribute of a shader. +// SetUniformAttr sets the value of a uniform attribute of a shader. The attribute is +// specified by the index in the Shader's uniform format. // -// If the attribute does not exist, this method returns false. +// If the uniform attribute does not exist in the Shader, this method returns false. // // Supplied value must correspond to the type of the attribute. Correct types are these // (right-hand is the type of the value): @@ -148,54 +149,54 @@ func (s *Shader) UniformFormat() AttrFormat { // No other types are supported. // // The shader must be bound before calling this method. -func (s *Shader) SetUniformAttr(attr Attr, value interface{}) (ok bool) { - if !s.uniformFmt.Contains(attr) { +func (s *Shader) SetUniformAttr(uniform int, value interface{}) (ok bool) { + if s.uniformLoc[uniform] < 0 { return false } - switch attr.Type { + switch s.uniformFmt[uniform].Type { case Int: value := value.(int32) - gl.Uniform1iv(s.uniforms[attr.Name], 1, &value) + gl.Uniform1iv(s.uniformLoc[uniform], 1, &value) case Float: value := value.(float32) - gl.Uniform1fv(s.uniforms[attr.Name], 1, &value) + gl.Uniform1fv(s.uniformLoc[uniform], 1, &value) case Vec2: value := value.(mgl32.Vec2) - gl.Uniform2fv(s.uniforms[attr.Name], 1, &value[0]) + gl.Uniform2fv(s.uniformLoc[uniform], 1, &value[0]) case Vec3: value := value.(mgl32.Vec3) - gl.Uniform3fv(s.uniforms[attr.Name], 1, &value[0]) + gl.Uniform3fv(s.uniformLoc[uniform], 1, &value[0]) case Vec4: value := value.(mgl32.Vec4) - gl.Uniform4fv(s.uniforms[attr.Name], 1, &value[0]) + gl.Uniform4fv(s.uniformLoc[uniform], 1, &value[0]) case Mat2: value := value.(mgl32.Mat2) - gl.UniformMatrix2fv(s.uniforms[attr.Name], 1, false, &value[0]) + gl.UniformMatrix2fv(s.uniformLoc[uniform], 1, false, &value[0]) case Mat23: value := value.(mgl32.Mat2x3) - gl.UniformMatrix2x3fv(s.uniforms[attr.Name], 1, false, &value[0]) + gl.UniformMatrix2x3fv(s.uniformLoc[uniform], 1, false, &value[0]) case Mat24: value := value.(mgl32.Mat2x4) - gl.UniformMatrix2x4fv(s.uniforms[attr.Name], 1, false, &value[0]) + gl.UniformMatrix2x4fv(s.uniformLoc[uniform], 1, false, &value[0]) case Mat3: value := value.(mgl32.Mat3) - gl.UniformMatrix3fv(s.uniforms[attr.Name], 1, false, &value[0]) + gl.UniformMatrix3fv(s.uniformLoc[uniform], 1, false, &value[0]) case Mat32: value := value.(mgl32.Mat3x2) - gl.UniformMatrix3x2fv(s.uniforms[attr.Name], 1, false, &value[0]) + gl.UniformMatrix3x2fv(s.uniformLoc[uniform], 1, false, &value[0]) case Mat34: value := value.(mgl32.Mat3x4) - gl.UniformMatrix3x4fv(s.uniforms[attr.Name], 1, false, &value[0]) + gl.UniformMatrix3x4fv(s.uniformLoc[uniform], 1, false, &value[0]) case Mat4: value := value.(mgl32.Mat4) - gl.UniformMatrix4fv(s.uniforms[attr.Name], 1, false, &value[0]) + gl.UniformMatrix4fv(s.uniformLoc[uniform], 1, false, &value[0]) case Mat42: value := value.(mgl32.Mat4x2) - gl.UniformMatrix4x2fv(s.uniforms[attr.Name], 1, false, &value[0]) + gl.UniformMatrix4x2fv(s.uniformLoc[uniform], 1, false, &value[0]) case Mat43: value := value.(mgl32.Mat4x3) - gl.UniformMatrix4x3fv(s.uniforms[attr.Name], 1, false, &value[0]) + gl.UniformMatrix4x3fv(s.uniformLoc[uniform], 1, false, &value[0]) default: panic("set uniform attr: invalid attribute type") } diff --git a/pixelgl/vertex.go b/pixelgl/vertex.go index b2a94f8..ea9fc5a 100644 --- a/pixelgl/vertex.go +++ b/pixelgl/vertex.go @@ -1,22 +1,13 @@ package pixelgl import ( + "fmt" "runtime" "github.com/go-gl/gl/v3.3-core/gl" - "github.com/go-gl/mathgl/mgl32" "github.com/pkg/errors" ) -// VertexData holds data of one vertex stored in vertex attributes. The values must match attribute -// types precisely. Here's the table of correct types (no other types are valid): -// -// Attr{Type: Float}: float32 -// Attr{Type: Vec2}: mgl32.Vec2 -// Attr{Type: Vec3}: mgl32.Vec3 -// Attr{Type: Vec4}: mgl32.Vec4 -type VertexData map[Attr]interface{} - // VertexSlice points to a portion of (or possibly whole) vertex array. It is used as a pointer, // contrary to Go's builtin slices. This is, so that append can be 'in-place'. That's for the good, // because Begin/End-ing a VertexSlice would become super confusing, if append returned a new @@ -53,6 +44,11 @@ func (vs *VertexSlice) VertexFormat() AttrFormat { return vs.va.format } +// Stride returns the number of float32 elements occupied by one vertex. +func (vs *VertexSlice) Stride() int { + return vs.va.stride / 4 +} + // Len returns the length of the VertexSlice. func (vs *VertexSlice) Len() int { return vs.j - vs.i @@ -117,26 +113,36 @@ func (vs VertexSlice) grow(len int) VertexSlice { // is not sufficient, a new, larger underlying vertex array will be allocated. The content of the // original VertexSlice will be copied to the new underlying vertex array. // +// The data is in the same format as with SetVertexData. +// // The VertexSlice is appended 'in-place', contrary Go's builtin slices. -func (vs *VertexSlice) Append(vertices ...VertexData) { +func (vs *VertexSlice) Append(data []float32) { vs.End() // vs must have been Begin-ed before calling this method - *vs = vs.grow(vs.Len() + len(vertices)) + *vs = vs.grow(vs.Len() + len(data)/vs.Stride()) vs.Begin() - vs.Slice(vs.Len()-len(vertices), vs.Len()).SetVertexData(vertices) + vs.Slice(vs.Len()-len(data)/vs.Stride(), vs.Len()).SetVertexData(data) } // SetVertexData sets the contents of the VertexSlice. // +// The data is a slice of float32's, where each vertex attribute occupies a certain number of +// elements. Namely, Float occupies 1, Vec2 occupies 2, Vec3 occupies 3 and Vec4 occupies 4. The +// attribues in the data slice must be in the same order as in the vertex format of this Vertex +// Slice. +// // If the length of vertices does not match the length of the VertexSlice, this methdo panics. -func (vs *VertexSlice) SetVertexData(vertices []VertexData) { - if len(vertices) != vs.Len() { +func (vs *VertexSlice) SetVertexData(data []float32) { + if len(data)/vs.Stride() != vs.Len() { + fmt.Println(len(data)/vs.Stride(), vs.Len()) panic("set vertex data: wrong length of vertices") } - vs.va.setVertexData(vs.i, vs.j, vertices) + vs.va.setVertexData(vs.i, vs.j, data) } // VertexData returns the contents of the VertexSlice. -func (vs *VertexSlice) VertexData() []VertexData { +// +// The data is in the same format as with SetVertexData. +func (vs *VertexSlice) VertexData() []float32 { return vs.va.vertexData(vs.i, vs.j) } @@ -160,7 +166,7 @@ type vertexArray struct { cap int format AttrFormat stride int - offset map[string]int + offset []int shader *Shader } @@ -187,19 +193,19 @@ func newVertexArray(shader *Shader, cap int) *vertexArray { cap: cap, format: shader.VertexFormat(), stride: shader.VertexFormat().Size(), - offset: make(map[string]int), + offset: make([]int, len(shader.VertexFormat())), shader: shader, } offset := 0 - for name, typ := range va.format { - switch typ { + for i, attr := range va.format { + switch attr.Type { case Float, Vec2, Vec3, Vec4: default: panic(errors.New("failed to create vertex array: invalid attribute type")) } - va.offset[name] = offset - offset += typ.Size() + va.offset[i] = offset + offset += attr.Type.Size() } gl.GenVertexArrays(1, &va.vao.obj) @@ -212,11 +218,11 @@ func newVertexArray(shader *Shader, cap int) *vertexArray { emptyData := make([]byte, cap*va.stride) gl.BufferData(gl.ARRAY_BUFFER, len(emptyData), gl.Ptr(emptyData), gl.DYNAMIC_DRAW) - for name, typ := range va.format { - loc := gl.GetAttribLocation(shader.program.obj, gl.Str(name+"\x00")) + for i, attr := range va.format { + loc := gl.GetAttribLocation(shader.program.obj, gl.Str(attr.Name+"\x00")) var size int32 - switch typ { + switch attr.Type { case Float: size = 1 case Vec2: @@ -233,7 +239,7 @@ func newVertexArray(shader *Shader, cap int) *vertexArray { gl.FLOAT, false, int32(va.stride), - gl.PtrOffset(va.offset[name]), + gl.PtrOffset(va.offset[i]), ) gl.EnableVertexAttribArray(uint32(loc)) } @@ -266,82 +272,20 @@ func (va *vertexArray) draw(i, j int) { gl.DrawArrays(gl.TRIANGLES, int32(i), int32(i+j)) } -func (va *vertexArray) setVertexData(i, j int, vertices []VertexData) { +func (va *vertexArray) setVertexData(i, j int, data []float32) { if j-i == 0 { // avoid setting 0 bytes of buffer data return } - - data := make([]float32, (j-i)*va.stride/4) - - for vertex := i; vertex < j; vertex++ { - for attr, value := range vertices[vertex] { - if !va.format.Contains(attr) { - continue - } - - offset := va.stride*vertex + va.offset[attr.Name] - - switch attr.Type { - case Float: - data[offset/4] = value.(float32) - case Vec2: - value := value.(mgl32.Vec2) - copy(data[offset/4:offset/4+attr.Type.Size()/4], value[:]) - case Vec3: - value := value.(mgl32.Vec3) - copy(data[offset/4:offset/4+attr.Type.Size()/4], value[:]) - case Vec4: - value := value.(mgl32.Vec4) - copy(data[offset/4:offset/4+attr.Type.Size()/4], value[:]) - default: - panic("set vertex: invalid attribute type") - } - } - } - gl.BufferSubData(gl.ARRAY_BUFFER, i*va.stride, len(data)*4, gl.Ptr(data)) } -func (va *vertexArray) vertexData(i, j int) []VertexData { +func (va *vertexArray) vertexData(i, j int) []float32 { if j-i == 0 { // avoid getting 0 bytes of buffer data return nil } - data := make([]float32, (j-i)*va.stride/4) - gl.GetBufferSubData(gl.ARRAY_BUFFER, i*va.stride, len(data)*4, gl.Ptr(data)) - - vertices := make([]VertexData, 0, (j - i)) - - for vertex := i; vertex < j; vertex++ { - values := make(map[Attr]interface{}) - - for name, typ := range va.format { - attr := Attr{name, typ} - offset := va.stride*vertex + va.offset[attr.Name] - - switch attr.Type { - case Float: - values[attr] = data[offset/4] - case Vec2: - var value mgl32.Vec2 - copy(value[:], data[offset/4:offset/4+attr.Type.Size()/4]) - values[attr] = value - case Vec3: - var value mgl32.Vec3 - copy(value[:], data[offset/4:offset/4+attr.Type.Size()/4]) - values[attr] = value - case Vec4: - var value mgl32.Vec4 - copy(value[:], data[offset/4:offset/4+attr.Type.Size()/4]) - values[attr] = value - } - } - - vertices = append(vertices, values) - } - - return vertices + return data } diff --git a/window.go b/window.go index 475a635..8d70cd9 100644 --- a/window.go +++ b/window.go @@ -363,11 +363,11 @@ func (w *Window) end() { type windowTriangles struct { w *Window vs *pixelgl.VertexSlice - data []pixelgl.VertexData + data []float32 } func (wt *windowTriangles) Len() int { - return len(wt.data) + return len(wt.data) / wt.vs.Stride() } func (wt *windowTriangles) Draw() { @@ -397,15 +397,14 @@ func (wt *windowTriangles) Draw() { func (wt *windowTriangles) resize(len int) { if len > wt.Len() { - newData := make([]pixelgl.VertexData, len-wt.Len()) - // default values - for i := range newData { - newData[i] = pixelgl.VertexData{ - colorVec4: mgl32.Vec4{1, 1, 1, 1}, - textureVec2: mgl32.Vec2{-1, -1}, - } + needAppend := len - wt.Len() + for i := 0; i < needAppend; i++ { + wt.data = append(wt.data, + 0, 0, + 1, 1, 1, 1, + -1, -1, + ) } - wt.data = append(wt.data, newData...) } if len < wt.Len() { wt.data = wt.data[:len] @@ -416,30 +415,24 @@ func (wt *windowTriangles) updateData(offset int, t Triangles) { if t, ok := t.(TrianglesPosition); ok { for i := offset; i < offset+t.Len(); i++ { px, py := t.Position(i).XY() - wt.data[i][positionVec2] = mgl32.Vec2{ - float32(px), - float32(py), - } + wt.data[i*wt.vs.Stride()+0] = float32(px) + wt.data[i*wt.vs.Stride()+1] = float32(py) } } if t, ok := t.(TrianglesColor); ok { for i := offset; i < offset+t.Len(); i++ { col := t.Color(i) - wt.data[i][colorVec4] = mgl32.Vec4{ - float32(col.R), - float32(col.G), - float32(col.B), - float32(col.A), - } + wt.data[i*wt.vs.Stride()+2] = float32(col.R) + wt.data[i*wt.vs.Stride()+3] = float32(col.G) + wt.data[i*wt.vs.Stride()+4] = float32(col.B) + wt.data[i*wt.vs.Stride()+5] = float32(col.A) } } if t, ok := t.(TrianglesTexture); ok { for i := offset; i < offset+t.Len(); i++ { tx, ty := t.Texture(i).XY() - wt.data[i][textureVec2] = mgl32.Vec2{ - float32(tx), - float32(ty), - } + wt.data[i*wt.vs.Stride()+6] = float32(tx) + wt.data[i*wt.vs.Stride()+7] = float32(ty) } } } @@ -448,11 +441,12 @@ func (wt *windowTriangles) submitData() { data := wt.data // avoid race condition pixelgl.DoNoBlock(func() { wt.vs.Begin() - if len(wt.data) > wt.vs.Len() { - wt.vs.Append(make([]pixelgl.VertexData, len(data)-wt.vs.Len())...) + dataLen := len(data) / wt.vs.Stride() + if dataLen > wt.vs.Len() { + wt.vs.Append(make([]float32, (dataLen-wt.vs.Len())*wt.vs.Stride())) } - if len(wt.data) < wt.vs.Len() { - wt.vs = wt.vs.Slice(0, len(wt.data)) + if dataLen < wt.vs.Len() { + wt.vs = wt.vs.Slice(0, dataLen) } wt.vs.SetVertexData(wt.data) wt.vs.End() @@ -481,23 +475,28 @@ func (wt *windowTriangles) Copy() Triangles { } func (wt *windowTriangles) Position(i int) Vec { - v := wt.data[i][positionVec2].(mgl32.Vec2) - return V(float64(v.X()), float64(v.Y())) + px := wt.data[i*wt.vs.Stride()+0] + py := wt.data[i*wt.vs.Stride()+1] + return V(float64(px), float64(py)) } func (wt *windowTriangles) Color(i int) NRGBA { - c := wt.data[i][colorVec4].(mgl32.Vec4) + r := wt.data[i*wt.vs.Stride()+2] + g := wt.data[i*wt.vs.Stride()+3] + b := wt.data[i*wt.vs.Stride()+4] + a := wt.data[i*wt.vs.Stride()+5] return NRGBA{ - R: float64(c.X()), - G: float64(c.Y()), - B: float64(c.Z()), - A: float64(c.W()), + R: float64(r), + G: float64(g), + B: float64(b), + A: float64(a), } } func (wt *windowTriangles) Texture(i int) Vec { - t := wt.data[i][textureVec2].(mgl32.Vec2) - return V(float64(t.X()), float64(t.Y())) + tx := wt.data[i*wt.vs.Stride()+6] + ty := wt.data[i*wt.vs.Stride()+7] + return V(float64(tx), float64(ty)) } // MakeTriangles generates a specialized copy of the supplied triangles that will draw onto this @@ -538,15 +537,26 @@ func (w *Window) SetMaskColor(c color.Color) { w.col = mgl32.Vec4{r, g, b, a} } +const ( + positionVec2 int = iota + colorVec4 + textureVec2 +) + var defaultVertexFormat = pixelgl.AttrFormat{ - "position": pixelgl.Vec2, - "color": pixelgl.Vec4, - "texture": pixelgl.Vec2, + positionVec2: {Name: "position", Type: pixelgl.Vec2}, + colorVec4: {Name: "color", Type: pixelgl.Vec4}, + textureVec2: {Name: "texture", Type: pixelgl.Vec2}, } +const ( + maskColorVec4 int = iota + transformMat3 +) + var defaultUniformFormat = pixelgl.AttrFormat{ - "maskColor": pixelgl.Vec4, - "transform": pixelgl.Mat3, + {Name: "maskColor", Type: pixelgl.Vec4}, + {Name: "transform", Type: pixelgl.Mat3}, } var defaultVertexShader = ` @@ -587,26 +597,3 @@ void main() { } } ` - -var ( - positionVec2 = pixelgl.Attr{ - Name: "position", - Type: pixelgl.Vec2, - } - colorVec4 = pixelgl.Attr{ - Name: "color", - Type: pixelgl.Vec4, - } - textureVec2 = pixelgl.Attr{ - Name: "texture", - Type: pixelgl.Vec2, - } - maskColorVec4 = pixelgl.Attr{ - Name: "maskColor", - Type: pixelgl.Vec4, - } - transformMat3 = pixelgl.Attr{ - Name: "transform", - Type: pixelgl.Mat3, - } -)