322 lines
9.3 KiB
Go
322 lines
9.3 KiB
Go
package pixelgl
|
|
|
|
import (
|
|
"unsafe"
|
|
|
|
"github.com/go-gl/gl/v3.3-core/gl"
|
|
"github.com/go-gl/mathgl/mgl32"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// VertexFormat defines a data format in a vertex buffer.
|
|
//
|
|
// Example:
|
|
//
|
|
// VertexFormat{"position": {Position, Vec2}, "colr": {Color, Vec4}, "texCoord": {TexCoord, Vec2}}
|
|
//
|
|
// Note: vertex array currently doesn't support matrices in vertex format.
|
|
type VertexFormat []Attr
|
|
|
|
// Size calculates the total size of a single vertex in this vertex format (sum of the sizes of all vertex attributes).
|
|
func (vf VertexFormat) Size() int {
|
|
total := 0
|
|
for _, attr := range vf {
|
|
total += attr.Type.Size()
|
|
}
|
|
return total
|
|
}
|
|
|
|
// VertexUsage specifies how often the vertex array data will be updated.
|
|
type VertexUsage int
|
|
|
|
const (
|
|
// StaticUsage means the data never or rarely gets updated.
|
|
StaticUsage VertexUsage = gl.STATIC_DRAW
|
|
|
|
// DynamicUsage means the data gets updated often.
|
|
DynamicUsage VertexUsage = gl.DYNAMIC_DRAW
|
|
|
|
// StreamUsage means the data gets updated every frame.
|
|
StreamUsage VertexUsage = gl.STREAM_DRAW
|
|
)
|
|
|
|
// VertexArray is an OpenGL vertex array object that also holds it's own vertex buffer object.
|
|
// From the user's points of view, VertexArray is an array of vertices that can be drawn.
|
|
type VertexArray struct {
|
|
enabled bool
|
|
parent Doer
|
|
vao, vbo, ebo uint32
|
|
vertexNum, indexNum int
|
|
format VertexFormat
|
|
usage VertexUsage
|
|
stride int
|
|
attrs map[Attr]int
|
|
}
|
|
|
|
// NewVertexArray creates a new empty vertex array and wraps another Doer around it.
|
|
//
|
|
// You cannot specify vertex attributes in this constructor, only their count. Use SetVertexAttribute* methods to
|
|
// set the vertex attributes. Use indices to specify how you want to combine vertices into triangles.
|
|
func NewVertexArray(parent Doer, format VertexFormat, usage VertexUsage, vertexNum int, indices []int) (*VertexArray, error) {
|
|
va := &VertexArray{
|
|
parent: parent,
|
|
format: format,
|
|
usage: usage,
|
|
vertexNum: vertexNum,
|
|
stride: format.Size(),
|
|
attrs: make(map[Attr]int),
|
|
}
|
|
|
|
offset := 0
|
|
for _, attr := range format {
|
|
switch attr.Type {
|
|
case Float, Vec2, Vec3, Vec4:
|
|
default:
|
|
return nil, errors.New("failed to create vertex array: invalid vertex format: invalid attribute type")
|
|
}
|
|
if _, ok := va.attrs[attr]; ok {
|
|
return nil, errors.New("failed to create vertex array: invalid vertex format: duplicate vertex attribute")
|
|
}
|
|
va.attrs[attr] = offset
|
|
offset += attr.Type.Size()
|
|
}
|
|
|
|
parent.Do(func(ctx Context) {
|
|
Do(func() {
|
|
gl.GenVertexArrays(1, &va.vao)
|
|
gl.BindVertexArray(va.vao)
|
|
|
|
gl.GenBuffers(1, &va.vbo)
|
|
gl.BindBuffer(gl.ARRAY_BUFFER, va.vbo)
|
|
|
|
emptyData := make([]byte, vertexNum*va.stride)
|
|
gl.BufferData(gl.ARRAY_BUFFER, len(emptyData), gl.Ptr(emptyData), uint32(usage))
|
|
|
|
gl.GenBuffers(1, &va.ebo)
|
|
|
|
offset := 0
|
|
for i, attr := range format {
|
|
var size int32
|
|
switch attr.Type {
|
|
case Float:
|
|
size = 1
|
|
case Vec2:
|
|
size = 2
|
|
case Vec3:
|
|
size = 3
|
|
case Vec4:
|
|
size = 4
|
|
}
|
|
|
|
gl.VertexAttribPointer(
|
|
uint32(i),
|
|
size,
|
|
gl.FLOAT,
|
|
false,
|
|
int32(va.stride),
|
|
gl.PtrOffset(offset),
|
|
)
|
|
gl.EnableVertexAttribArray(uint32(i))
|
|
offset += attr.Type.Size()
|
|
}
|
|
|
|
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, va.ebo) // need to bind EBO, so that VAO registers it
|
|
|
|
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
|
|
gl.BindVertexArray(0)
|
|
|
|
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)
|
|
})
|
|
})
|
|
|
|
va.SetIndices(indices)
|
|
|
|
return va, nil
|
|
}
|
|
|
|
// Delete deletes a vertex array and it's associated vertex buffer. Don't use a vertex array after deletion.
|
|
func (va *VertexArray) Delete() {
|
|
va.parent.Do(func(ctx Context) {
|
|
DoNoBlock(func() {
|
|
gl.DeleteVertexArrays(1, &va.vao)
|
|
gl.DeleteBuffers(1, &va.vbo)
|
|
})
|
|
})
|
|
}
|
|
|
|
// ID returns an OpenGL identifier of a vertex array.
|
|
func (va *VertexArray) ID() uint32 {
|
|
return va.vao
|
|
}
|
|
|
|
// VertexNum returns the number of vertices in a vertex array.
|
|
func (va *VertexArray) VertexNum() int {
|
|
return va.vertexNum
|
|
}
|
|
|
|
// VertexFormat returns the format of the vertices inside a vertex array.
|
|
//
|
|
// Do not change this format!
|
|
func (va *VertexArray) VertexFormat() VertexFormat {
|
|
return va.format
|
|
}
|
|
|
|
// VertexUsage returns the usage of the verteices inside a vertex array.
|
|
func (va *VertexArray) VertexUsage() VertexUsage {
|
|
return va.usage
|
|
}
|
|
|
|
// Draw draws a vertex array.
|
|
func (va *VertexArray) Draw() {
|
|
va.Do(func(Context) {})
|
|
}
|
|
|
|
// SetIndices sets the indices of triangles to be drawn. Triangles will be formed from the vertices of the array
|
|
// as defined by these indices. The first drawn triangle is specified by the first three indices, the second by
|
|
// the fourth through sixth and so on.
|
|
func (va *VertexArray) SetIndices(indices []int) {
|
|
if len(indices)%3 != 0 {
|
|
panic("vertex array set indices: number of indices not divisible by 3")
|
|
}
|
|
indices32 := make([]uint32, len(indices))
|
|
for i := range indices32 {
|
|
indices32[i] = uint32(indices[i])
|
|
}
|
|
va.indexNum = len(indices32)
|
|
DoNoBlock(func() {
|
|
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, va.ebo)
|
|
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, 4*len(indices32), gl.Ptr(indices32), uint32(va.usage))
|
|
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)
|
|
})
|
|
}
|
|
|
|
// SetVertex sets the value of all attributes of a vertex.
|
|
// Argument data must be a slice/array containing the new vertex data.
|
|
func (va *VertexArray) SetVertex(vertex int, data interface{}) {
|
|
if vertex < 0 || vertex >= va.vertexNum {
|
|
panic("set vertex error: invalid vertex index")
|
|
}
|
|
DoNoBlock(func() {
|
|
gl.BindBuffer(gl.ARRAY_BUFFER, va.vbo)
|
|
|
|
offset := va.stride * vertex
|
|
gl.BufferSubData(gl.ARRAY_BUFFER, offset, va.format.Size(), gl.Ptr(data))
|
|
|
|
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
|
|
})
|
|
}
|
|
|
|
func (va *VertexArray) checkVertex(vertex int) {
|
|
if vertex < 0 || vertex >= va.vertexNum {
|
|
panic("invalid vertex index")
|
|
}
|
|
}
|
|
|
|
// SetVertexAttributeFloat sets the value of a specified vertex attribute Attr{Purpose: purpose, Type: Float} of type Float
|
|
// of the specified vertex.
|
|
//
|
|
// This function returns false if the specified vertex attribute does not exist. Note that the function panics if
|
|
// the vertex if out of range.
|
|
func (va *VertexArray) SetVertexAttributeFloat(vertex int, purpose AttrPurpose, value float32) (ok bool) {
|
|
va.checkVertex(vertex)
|
|
attr := Attr{Purpose: purpose, Type: Float}
|
|
if _, ok := va.attrs[attr]; !ok {
|
|
return false
|
|
}
|
|
DoNoBlock(func() {
|
|
gl.BindBuffer(gl.ARRAY_BUFFER, va.vbo)
|
|
|
|
offset := va.stride*vertex + va.attrs[attr]
|
|
gl.BufferSubData(gl.ARRAY_BUFFER, offset, attr.Type.Size(), unsafe.Pointer(&value))
|
|
|
|
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
|
|
})
|
|
return true
|
|
}
|
|
|
|
// SetVertexAttributeVec2 sets the value of a specified vertex attribute Attr{Purpose: purpose, Type: Vec2} of type Vec2
|
|
// of the specified vertex.
|
|
//
|
|
// This function returns false if the specified vertex attribute does not exist. Note that the function panics if
|
|
// the vertex if out of range.
|
|
func (va *VertexArray) SetVertexAttributeVec2(vertex int, purpose AttrPurpose, value mgl32.Vec2) (ok bool) {
|
|
va.checkVertex(vertex)
|
|
attr := Attr{Purpose: purpose, Type: Vec2}
|
|
if _, ok := va.attrs[attr]; !ok {
|
|
return false
|
|
}
|
|
DoNoBlock(func() {
|
|
gl.BindBuffer(gl.ARRAY_BUFFER, va.vbo)
|
|
|
|
offset := va.stride*vertex + va.attrs[attr]
|
|
gl.BufferSubData(gl.ARRAY_BUFFER, offset, attr.Type.Size(), unsafe.Pointer(&value))
|
|
|
|
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
|
|
})
|
|
return true
|
|
}
|
|
|
|
// SetVertexAttributeVec3 sets the value of a specified vertex attribute Attr{Purpose: purpose, Type: Vec3} of type Vec3
|
|
// of the specified vertex.
|
|
//
|
|
// This function returns false if the specified vertex attribute does not exist. Note that the function panics if
|
|
// the vertex if out of range.
|
|
func (va *VertexArray) SetVertexAttributeVec3(vertex int, purpose AttrPurpose, value mgl32.Vec3) (ok bool) {
|
|
va.checkVertex(vertex)
|
|
attr := Attr{Purpose: purpose, Type: Vec3}
|
|
if _, ok := va.attrs[attr]; !ok {
|
|
return false
|
|
}
|
|
DoNoBlock(func() {
|
|
gl.BindBuffer(gl.ARRAY_BUFFER, va.vbo)
|
|
|
|
offset := va.stride*vertex + va.attrs[attr]
|
|
gl.BufferSubData(gl.ARRAY_BUFFER, offset, attr.Type.Size(), unsafe.Pointer(&value))
|
|
|
|
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
|
|
})
|
|
return true
|
|
}
|
|
|
|
// SetVertexAttributeVec4 sets the value of a specified vertex attribute Attr{Purpose: purpose, Type: Vec4} of type Vec4
|
|
// of the specified vertex.
|
|
//
|
|
// This function returns false if the specified vertex attribute does not exist. Note that the function panics if
|
|
// the vertex if out of range.
|
|
func (va *VertexArray) SetVertexAttributeVec4(vertex int, purpose AttrPurpose, value mgl32.Vec4) (ok bool) {
|
|
va.checkVertex(vertex)
|
|
attr := Attr{Purpose: purpose, Type: Vec4}
|
|
if _, ok := va.attrs[attr]; !ok {
|
|
return false
|
|
}
|
|
DoNoBlock(func() {
|
|
gl.BindBuffer(gl.ARRAY_BUFFER, va.vbo)
|
|
|
|
offset := va.stride*vertex + va.attrs[attr]
|
|
gl.BufferSubData(gl.ARRAY_BUFFER, offset, attr.Type.Size(), unsafe.Pointer(&value))
|
|
|
|
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
|
|
})
|
|
return true
|
|
}
|
|
|
|
// Do binds a vertex arrray and it's associated vertex buffer, executes sub, and unbinds the vertex array and it's vertex buffer.
|
|
func (va *VertexArray) Do(sub func(Context)) {
|
|
va.parent.Do(func(ctx Context) {
|
|
if va.enabled {
|
|
sub(ctx)
|
|
return
|
|
}
|
|
DoNoBlock(func() {
|
|
gl.BindVertexArray(va.vao)
|
|
})
|
|
va.enabled = true
|
|
sub(ctx)
|
|
va.enabled = false
|
|
DoNoBlock(func() {
|
|
gl.DrawElements(gl.TRIANGLES, int32(va.indexNum), gl.UNSIGNED_INT, gl.PtrOffset(0))
|
|
gl.BindVertexArray(0)
|
|
})
|
|
})
|
|
}
|