Compare commits

...

8 Commits

6 changed files with 1272 additions and 80 deletions

View File

@ -1,23 +1,23 @@
package buffers
import (
"fmt"
"github.com/bloeys/nmage/assert"
"github.com/bloeys/nmage/logging"
"github.com/go-gl/gl/v4.1-core/gl"
)
//Element represents an element that makes up a buffer (e.g. Vec3 at an offset of 12 bytes)
// Element represents an element that makes up a buffer (e.g. Vec3 at an offset of 12 bytes)
type Element struct {
Offset int
ElementType
}
//ElementType is the type of an element thats makes up a buffer (e.g. Vec3)
type ElementType int
// ElementType is the type of an element thats makes up a buffer (e.g. Vec3)
type ElementType uint8
const (
DataTypeUnknown ElementType = iota
DataTypeUint32
DataTypeInt32
DataTypeFloat32
@ -25,35 +25,54 @@ const (
DataTypeVec2
DataTypeVec3
DataTypeVec4
DataTypeMat2
DataTypeMat3
DataTypeMat4
DataTypeStruct
)
func (dt ElementType) GLType() uint32 {
switch dt {
case DataTypeUint32:
return gl.UNSIGNED_INT
case DataTypeInt32:
return gl.INT
case DataTypeFloat32:
fallthrough
case DataTypeVec2:
fallthrough
case DataTypeVec3:
fallthrough
case DataTypeVec4:
fallthrough
case DataTypeMat2:
fallthrough
case DataTypeMat3:
fallthrough
case DataTypeMat4:
return gl.FLOAT
case DataTypeStruct:
logging.ErrLog.Fatalf("ElementType.GLType of DataTypeStruct is not supported")
return 0
default:
assert.T(false, fmt.Sprintf("Unknown data type passed. DataType '%v'", dt))
assert.T(false, "Unknown data type passed. DataType '%d'", dt)
return 0
}
}
//CompSize returns the size in bytes for one component of the type (e.g. for Vec2 its 4)
// CompSize returns the size in bytes for one component of the type (e.g. for Vec2 its 4).
// Bools return 1, although in layout=std140 its 4
func (dt ElementType) CompSize() int32 {
switch dt {
case DataTypeUint32:
fallthrough
case DataTypeFloat32:
@ -65,15 +84,25 @@ func (dt ElementType) CompSize() int32 {
case DataTypeVec3:
fallthrough
case DataTypeVec4:
fallthrough
case DataTypeMat2:
fallthrough
case DataTypeMat3:
fallthrough
case DataTypeMat4:
return 4
case DataTypeStruct:
logging.ErrLog.Fatalf("ElementType.CompSize of DataTypeStruct is not supported")
return 0
default:
assert.T(false, fmt.Sprintf("Unknown data type passed. DataType '%v'", dt))
assert.T(false, "Unknown data type passed. DataType '%d'", dt)
return 0
}
}
//CompCount returns the number of components in the element (e.g. for Vec2 its 2)
// CompCount returns the number of components in the element (e.g. for Vec2 its 2)
func (dt ElementType) CompCount() int32 {
switch dt {
@ -91,16 +120,28 @@ func (dt ElementType) CompCount() int32 {
case DataTypeVec4:
return 4
case DataTypeMat2:
return 2 * 2
case DataTypeMat3:
return 3 * 3
case DataTypeMat4:
return 4 * 4
case DataTypeStruct:
logging.ErrLog.Fatalf("ElementType.CompCount of DataTypeStruct is not supported")
return 0
default:
assert.T(false, fmt.Sprintf("Unknown data type passed. DataType '%v'", dt))
assert.T(false, "Unknown data type passed. DataType '%d'", dt)
return 0
}
}
//Size returns the total size in bytes (e.g. for vec3 its 3*4=12 bytes)
// Size returns the total size in bytes (e.g. for vec3 its 3*4=12 bytes)
func (dt ElementType) Size() int32 {
switch dt {
case DataTypeUint32:
fallthrough
case DataTypeFloat32:
@ -115,8 +156,123 @@ func (dt ElementType) Size() int32 {
case DataTypeVec4:
return 4 * 4
case DataTypeMat2:
return 2 * 2 * 4
case DataTypeMat3:
return 3 * 3 * 4
case DataTypeMat4:
return 4 * 4 * 4
case DataTypeStruct:
logging.ErrLog.Fatalf("ElementType.Size of DataTypeStruct is not supported")
return 0
default:
assert.T(false, fmt.Sprintf("Unknown data type passed. DataType '%v'", dt))
assert.T(false, "Unknown data type passed. DataType '%d'", dt)
return 0
}
}
func (dt ElementType) GlStd140BaseAlignment() uint8 {
switch dt {
case DataTypeUint32:
fallthrough
case DataTypeFloat32:
fallthrough
case DataTypeInt32:
return 4
case DataTypeVec2:
return 4 * 2
// Vec3 has the same alignment as vec4
case DataTypeVec3:
fallthrough
case DataTypeVec4:
return 4 * 4
// Matrices follow: (vec4Alignment) * numColumns
case DataTypeMat2:
return (4 * 4) * 2
case DataTypeMat3:
return (4 * 4) * 3
case DataTypeMat4:
return (4 * 4) * 4
case DataTypeStruct:
logging.ErrLog.Fatalf("ElementType.GlStd140BaseAlignment of DataTypeStruct is not supported")
return 0
default:
assert.T(false, "Unknown data type passed. DataType '%d'", dt)
return 0
}
}
func (dt ElementType) GlStd140AlignmentBoundary() uint16 {
switch dt {
case DataTypeUint32:
fallthrough
case DataTypeFloat32:
fallthrough
case DataTypeInt32:
return 4
case DataTypeVec2:
return 8
case DataTypeVec3:
fallthrough
case DataTypeVec4:
fallthrough
case DataTypeMat2:
fallthrough
case DataTypeMat3:
fallthrough
case DataTypeMat4:
fallthrough
case DataTypeStruct:
return 16
default:
assert.T(false, "Unknown data type passed. DataType '%d'", dt)
return 0
}
}
func (dt ElementType) String() string {
switch dt {
case DataTypeUint32:
return "uint32"
case DataTypeFloat32:
return "float32"
case DataTypeInt32:
return "int32"
case DataTypeVec2:
return "Vec2"
case DataTypeVec3:
return "Vec3"
case DataTypeVec4:
return "Vec4"
case DataTypeMat2:
return "Mat2"
case DataTypeMat3:
return "Mat3"
case DataTypeMat4:
return "Mat4"
case DataTypeStruct:
return "Struct"
default:
return "Unknown"
}
}

702
buffers/uniform_buffer.go Executable file
View File

@ -0,0 +1,702 @@
package buffers
import (
"math"
"reflect"
"github.com/bloeys/gglm/gglm"
"github.com/bloeys/nmage/assert"
"github.com/bloeys/nmage/logging"
"github.com/go-gl/gl/v4.1-core/gl"
)
type UniformBufferFieldInput struct {
Id uint16
Type ElementType
// Count should be set in case this field is an array of type `[Count]Type`.
// Count=0 is valid and is equivalent to Count=1, which means the type is NOT an array, but a single field.
Count uint16
// Subfields is used when type is a struct, in which case it holds the fields of the struct.
// Ids do not have to be unique across structs.
Subfields []UniformBufferFieldInput
}
type UniformBufferField struct {
Id uint16
AlignedOffset uint16
// Count should be set in case this field is an array of type `[Count]Type`.
// Count=0 is valid and is equivalent to Count=1, which means the type is NOT an array, but a single field.
Count uint16
Type ElementType
// Subfields is used when type is a struct, in which case it holds the fields of the struct.
// Ids do not have to be unique across structs.
Subfields []UniformBufferField
}
type UniformBuffer struct {
Id uint32
// Size is the allocated memory in bytes on the GPU for this uniform buffer
Size uint32
Fields []UniformBufferField
}
func (ub *UniformBuffer) Bind() {
gl.BindBuffer(gl.UNIFORM_BUFFER, ub.Id)
}
func (ub *UniformBuffer) UnBind() {
gl.BindBuffer(gl.UNIFORM_BUFFER, 0)
}
func (ub *UniformBuffer) SetBindPoint(bindPointIndex uint32) {
gl.BindBufferBase(gl.UNIFORM_BUFFER, bindPointIndex, ub.Id)
}
func addUniformBufferFieldsToArray(startAlignedOffset uint16, arrayToAddTo *[]UniformBufferField, fieldsToAdd []UniformBufferFieldInput) (totalSize uint32) {
if len(fieldsToAdd) == 0 {
return 0
}
// This function is recursive so only size the array once
if cap(*arrayToAddTo) == 0 {
*arrayToAddTo = make([]UniformBufferField, 0, len(fieldsToAdd))
}
var alignedOffset uint16 = 0
fieldIdToTypeMap := make(map[uint16]ElementType, len(fieldsToAdd))
for i := 0; i < len(fieldsToAdd); i++ {
f := fieldsToAdd[i]
if f.Count == 0 {
f.Count = 1
}
existingFieldType, ok := fieldIdToTypeMap[f.Id]
assert.T(!ok, "Uniform buffer field id is reused within the same uniform buffer. FieldId=%d was first used on a field with type=%s and then used on a different field with type=%s\n", f.Id, existingFieldType.String(), f.Type.String())
// To understand this take an example. Say we have a total offset of 100 and we are adding a vec4.
// Vec4s must be aligned to a 16 byte boundary but 100 is not (100 % 16 != 0).
//
// To fix this, we take the alignment error which is alignErr=100 % 16=4, but this is error to the nearest
// boundary, which is below the offset.
//
// To get the nearest boundary larger than the offset we can:
// offset + (boundary - alignErr) == 100 + (16 - 4) == 112; 112 % 16 == 0, meaning its a boundary
//
// Note that arrays of scalars/vectors are always aligned to 16 bytes, like a vec4
var alignmentBoundary uint16 = 16
if f.Count == 1 {
alignmentBoundary = f.Type.GlStd140AlignmentBoundary()
}
alignmentError := alignedOffset % alignmentBoundary
if alignmentError != 0 {
alignedOffset += alignmentBoundary - alignmentError
}
newField := UniformBufferField{Id: f.Id, Type: f.Type, AlignedOffset: startAlignedOffset + alignedOffset, Count: f.Count}
*arrayToAddTo = append(*arrayToAddTo, newField)
// Prepare aligned offset for the next field.
//
// Matrices are treated as an array of column vectors, where each column is a vec4,
// that's why we have a multiplier depending on how many columns we have when calculating
// the offset
multiplier := uint16(1)
if f.Type == DataTypeMat2 {
multiplier = 2
} else if f.Type == DataTypeMat3 {
multiplier = 3
} else if f.Type == DataTypeMat4 {
multiplier = 4
}
if f.Type == DataTypeStruct {
subfieldsAlignedOffset := uint16(addUniformBufferFieldsToArray(startAlignedOffset+alignedOffset, arrayToAddTo, f.Subfields))
// Pad structs to 16 byte boundary
subfieldsAlignmentError := subfieldsAlignedOffset % 16
if subfieldsAlignmentError != 0 {
subfieldsAlignedOffset += 16 - subfieldsAlignmentError
}
alignedOffset += subfieldsAlignedOffset * f.Count
} else {
alignedOffset = newField.AlignedOffset + alignmentBoundary*f.Count*multiplier - startAlignedOffset
}
}
return uint32(alignedOffset)
}
func (ub *UniformBuffer) getField(fieldId uint16, fieldType ElementType) UniformBufferField {
for i := 0; i < len(ub.Fields); i++ {
f := ub.Fields[i]
if f.Id != fieldId {
continue
}
assert.T(f.Type == fieldType, "Uniform buffer field id is reused within the same uniform buffer. FieldId=%d was first used on a field with type=%v, but is now being used on a field with type=%v\n", fieldId, f.Type.String(), fieldType.String())
return f
}
logging.ErrLog.Panicf("couldn't find uniform buffer field of id=%d and type=%s\n", fieldId, fieldType.String())
return UniformBufferField{}
}
func (ub *UniformBuffer) SetInt32(fieldId uint16, val int32) {
f := ub.getField(fieldId, DataTypeInt32)
gl.BufferSubData(gl.UNIFORM_BUFFER, int(f.AlignedOffset), 4, gl.Ptr(&val))
}
func (ub *UniformBuffer) SetUint32(fieldId uint16, val uint32) {
f := ub.getField(fieldId, DataTypeUint32)
gl.BufferSubData(gl.UNIFORM_BUFFER, int(f.AlignedOffset), 4, gl.Ptr(&val))
}
func (ub *UniformBuffer) SetFloat32(fieldId uint16, val float32) {
f := ub.getField(fieldId, DataTypeFloat32)
gl.BufferSubData(gl.UNIFORM_BUFFER, int(f.AlignedOffset), 4, gl.Ptr(&val))
}
func (ub *UniformBuffer) SetVec2(fieldId uint16, val *gglm.Vec2) {
f := ub.getField(fieldId, DataTypeVec2)
gl.BufferSubData(gl.UNIFORM_BUFFER, int(f.AlignedOffset), 4*2, gl.Ptr(&val.Data[0]))
}
func (ub *UniformBuffer) SetVec3(fieldId uint16, val *gglm.Vec3) {
f := ub.getField(fieldId, DataTypeVec3)
gl.BufferSubData(gl.UNIFORM_BUFFER, int(f.AlignedOffset), 4*3, gl.Ptr(&val.Data[0]))
}
func (ub *UniformBuffer) SetVec4(fieldId uint16, val *gglm.Vec4) {
f := ub.getField(fieldId, DataTypeVec4)
gl.BufferSubData(gl.UNIFORM_BUFFER, int(f.AlignedOffset), 4*4, gl.Ptr(&val.Data[0]))
}
func (ub *UniformBuffer) SetMat2(fieldId uint16, val *gglm.Mat2) {
f := ub.getField(fieldId, DataTypeMat2)
gl.BufferSubData(gl.UNIFORM_BUFFER, int(f.AlignedOffset), 4*4, gl.Ptr(&val.Data[0][0]))
}
func (ub *UniformBuffer) SetMat3(fieldId uint16, val *gglm.Mat3) {
f := ub.getField(fieldId, DataTypeMat3)
gl.BufferSubData(gl.UNIFORM_BUFFER, int(f.AlignedOffset), 4*9, gl.Ptr(&val.Data[0][0]))
}
func (ub *UniformBuffer) SetMat4(fieldId uint16, val *gglm.Mat4) {
f := ub.getField(fieldId, DataTypeMat4)
gl.BufferSubData(gl.UNIFORM_BUFFER, int(f.AlignedOffset), 4*16, gl.Ptr(&val.Data[0][0]))
}
func (ub *UniformBuffer) SetStruct(inputStruct any) {
setStruct(ub.Fields, make([]byte, ub.Size), inputStruct, 1000_000, false)
}
func setStruct(fields []UniformBufferField, buf []byte, inputStruct any, maxFieldsToConsume int, onlyBufWrite bool) (bytesWritten, fieldsConsumed int) {
if len(fields) == 0 {
return
}
if inputStruct == nil {
logging.ErrLog.Panicf("UniformBuffer.SetStruct called with a value that is nil")
}
structVal := reflect.ValueOf(inputStruct)
if structVal.Kind() != reflect.Struct {
logging.ErrLog.Panicf("UniformBuffer.SetStruct called with a value that is not a struct. Val=%v\n", inputStruct)
}
structFieldIndex := 0
// structFieldCount := structVal.NumField()
for fieldIndex := 0; fieldIndex < len(fields) && fieldIndex < maxFieldsToConsume; fieldIndex++ {
ubField := &fields[fieldIndex]
valField := structVal.Field(structFieldIndex)
fieldsConsumed++
structFieldIndex++
kind := valField.Kind()
if kind == reflect.Pointer {
valField = valField.Elem()
}
var elementType reflect.Type
isArray := kind == reflect.Slice || kind == reflect.Array
if isArray {
elementType = valField.Type().Elem()
} else {
elementType = valField.Type()
}
if isArray {
assert.T(valField.Len() == int(ubField.Count), "ubo field of id=%d is an array/slice field of length=%d but got input of length=%d\n", ubField.Id, ubField.Count, valField.Len())
}
typeMatches := false
bytesWritten = int(ubField.AlignedOffset)
switch ubField.Type {
case DataTypeUint32:
typeMatches = elementType.Name() == "uint32"
if typeMatches {
if isArray {
Write32BitIntegerSliceToByteBufWithAlignment(buf, &bytesWritten, 16, valField.Slice(0, valField.Len()).Interface().([]uint32))
} else {
Write32BitIntegerToByteBuf(buf, &bytesWritten, uint32(valField.Uint()))
}
}
case DataTypeFloat32:
typeMatches = elementType.Name() == "float32"
if typeMatches {
if isArray {
WriteF32SliceToByteBufWithAlignment(buf, &bytesWritten, 16, valField.Slice(0, valField.Len()).Interface().([]float32))
} else {
WriteF32ToByteBuf(buf, &bytesWritten, float32(valField.Float()))
}
}
case DataTypeInt32:
typeMatches = elementType.Name() == "int32"
if typeMatches {
if isArray {
Write32BitIntegerSliceToByteBufWithAlignment(buf, &bytesWritten, 16, valField.Slice(0, valField.Len()).Interface().([]int32))
} else {
Write32BitIntegerToByteBuf(buf, &bytesWritten, uint32(valField.Int()))
}
}
case DataTypeVec2:
typeMatches = elementType.Name() == "Vec2"
if typeMatches {
if isArray {
WriteVec2SliceToByteBufWithAlignment(buf, &bytesWritten, 16, valField.Slice(0, valField.Len()).Interface().([]gglm.Vec2))
} else {
v2 := valField.Interface().(gglm.Vec2)
WriteF32SliceToByteBuf(buf, &bytesWritten, v2.Data[:])
}
}
case DataTypeVec3:
typeMatches = elementType.Name() == "Vec3"
if typeMatches {
if isArray {
WriteVec3SliceToByteBufWithAlignment(buf, &bytesWritten, 16, valField.Slice(0, valField.Len()).Interface().([]gglm.Vec3))
} else {
v3 := valField.Interface().(gglm.Vec3)
WriteF32SliceToByteBuf(buf, &bytesWritten, v3.Data[:])
}
}
case DataTypeVec4:
typeMatches = elementType.Name() == "Vec4"
if typeMatches {
if isArray {
WriteVec4SliceToByteBufWithAlignment(buf, &bytesWritten, 16, valField.Slice(0, valField.Len()).Interface().([]gglm.Vec4))
} else {
v3 := valField.Interface().(gglm.Vec4)
WriteF32SliceToByteBuf(buf, &bytesWritten, v3.Data[:])
}
}
case DataTypeMat2:
typeMatches = elementType.Name() == "Mat2"
if typeMatches {
if isArray {
m2Arr := valField.Interface().([]gglm.Mat2)
WriteMat2SliceToByteBufWithAlignment(buf, &bytesWritten, 16*2, m2Arr)
} else {
m := valField.Interface().(gglm.Mat2)
WriteF32SliceToByteBuf(buf, &bytesWritten, m.Data[0][:])
WriteF32SliceToByteBuf(buf, &bytesWritten, m.Data[1][:])
}
}
case DataTypeMat3:
typeMatches = elementType.Name() == "Mat3"
if typeMatches {
if isArray {
m3Arr := valField.Interface().([]gglm.Mat3)
WriteMat3SliceToByteBufWithAlignment(buf, &bytesWritten, 16*3, m3Arr)
} else {
m := valField.Interface().(gglm.Mat3)
WriteF32SliceToByteBuf(buf, &bytesWritten, m.Data[0][:])
WriteF32SliceToByteBuf(buf, &bytesWritten, m.Data[1][:])
WriteF32SliceToByteBuf(buf, &bytesWritten, m.Data[2][:])
}
}
case DataTypeMat4:
typeMatches = elementType.Name() == "Mat4"
if typeMatches {
if isArray {
m4Arr := valField.Interface().([]gglm.Mat4)
WriteMat4SliceToByteBufWithAlignment(buf, &bytesWritten, 16*4, m4Arr)
} else {
m := valField.Interface().(gglm.Mat4)
WriteF32SliceToByteBuf(buf, &bytesWritten, m.Data[0][:])
WriteF32SliceToByteBuf(buf, &bytesWritten, m.Data[1][:])
WriteF32SliceToByteBuf(buf, &bytesWritten, m.Data[2][:])
WriteF32SliceToByteBuf(buf, &bytesWritten, m.Data[3][:])
}
}
case DataTypeStruct:
typeMatches = kind == reflect.Struct
if typeMatches {
setStructBytesWritten, setStructFieldsConsumed := setStruct(fields[fieldIndex+1:], buf, valField.Interface(), valField.NumField(), true)
bytesWritten += setStructBytesWritten
fieldIndex += setStructFieldsConsumed
fieldsConsumed += setStructFieldsConsumed
}
default:
assert.T(false, "Unknown uniform buffer data type passed. DataType '%d'", ubField.Type)
}
if !typeMatches {
logging.ErrLog.Panicf("Struct field ordering and types must match uniform buffer fields, but at field index %d got UniformBufferField=%v but a struct field of type %s\n", fieldIndex, ubField, valField.String())
}
}
if bytesWritten == 0 {
return 0, fieldsConsumed
}
if !onlyBufWrite {
gl.BufferSubData(gl.UNIFORM_BUFFER, 0, bytesWritten, gl.Ptr(&buf[0]))
}
return bytesWritten - int(fields[0].AlignedOffset), fieldsConsumed
}
func Write32BitIntegerToByteBuf[T uint32 | int32](buf []byte, startIndex *int, val T) {
assert.T(*startIndex+4 <= len(buf), "failed to write uint32/int32 to buffer because the buffer doesn't have enough space. Start index=%d, Buffer length=%d", *startIndex, len(buf))
buf[*startIndex] = byte(val)
buf[*startIndex+1] = byte(val >> 8)
buf[*startIndex+2] = byte(val >> 16)
buf[*startIndex+3] = byte(val >> 24)
*startIndex += 4
}
func Write32BitIntegerSliceToByteBufWithAlignment[T uint32 | int32](buf []byte, startIndex *int, alignmentPerField int, vals []T) {
assert.T(*startIndex+len(vals)*alignmentPerField <= len(buf), "failed to write uint32/int32 with custom alignment=%d to buffer because the buffer doesn't have enough space. Start index=%d, Buffer length=%d, but needs %d bytes free", alignmentPerField, *startIndex, len(buf), len(vals)*alignmentPerField)
for i := 0; i < len(vals); i++ {
val := vals[i]
buf[*startIndex] = byte(val)
buf[*startIndex+1] = byte(val >> 8)
buf[*startIndex+2] = byte(val >> 16)
buf[*startIndex+3] = byte(val >> 24)
*startIndex += alignmentPerField
}
}
func WriteF32ToByteBuf(buf []byte, startIndex *int, val float32) {
assert.T(*startIndex+4 <= len(buf), "failed to write float32 to buffer because the buffer doesn't have enough space. Start index=%d, Buffer length=%d", *startIndex, len(buf))
bits := math.Float32bits(val)
buf[*startIndex] = byte(bits)
buf[*startIndex+1] = byte(bits >> 8)
buf[*startIndex+2] = byte(bits >> 16)
buf[*startIndex+3] = byte(bits >> 24)
*startIndex += 4
}
func WriteF32SliceToByteBuf(buf []byte, startIndex *int, vals []float32) {
assert.T(*startIndex+len(vals)*4 <= len(buf), "failed to write slice of float32 to buffer because the buffer doesn't have enough space. Start index=%d, Buffer length=%d, but needs %d bytes free", *startIndex, len(buf), len(vals)*4)
for i := 0; i < len(vals); i++ {
bits := math.Float32bits(vals[i])
buf[*startIndex] = byte(bits)
buf[*startIndex+1] = byte(bits >> 8)
buf[*startIndex+2] = byte(bits >> 16)
buf[*startIndex+3] = byte(bits >> 24)
*startIndex += 4
}
}
func WriteF32SliceToByteBufWithAlignment(buf []byte, startIndex *int, alignmentPerField int, vals []float32) {
assert.T(*startIndex+len(vals)*alignmentPerField <= len(buf), "failed to write slice of float32 with custom alignment=%d to buffer because the buffer doesn't have enough space. Start index=%d, Buffer length=%d, but needs %d bytes free", alignmentPerField, *startIndex, len(buf), len(vals)*alignmentPerField)
for i := 0; i < len(vals); i++ {
bits := math.Float32bits(vals[i])
buf[*startIndex] = byte(bits)
buf[*startIndex+1] = byte(bits >> 8)
buf[*startIndex+2] = byte(bits >> 16)
buf[*startIndex+3] = byte(bits >> 24)
*startIndex += alignmentPerField
}
}
func WriteVec2SliceToByteBufWithAlignment(buf []byte, startIndex *int, alignmentPerVector int, vals []gglm.Vec2) {
assert.T(*startIndex+len(vals)*alignmentPerVector <= len(buf), "failed to write slice of gglm.Vec2 with custom alignment=%d to buffer because the buffer doesn't have enough space. Start index=%d, Buffer length=%d, but needs %d bytes free", alignmentPerVector, *startIndex, len(buf), len(vals)*alignmentPerVector)
for i := 0; i < len(vals); i++ {
bitsX := math.Float32bits(vals[i].X())
bitsY := math.Float32bits(vals[i].Y())
buf[*startIndex] = byte(bitsX)
buf[*startIndex+1] = byte(bitsX >> 8)
buf[*startIndex+2] = byte(bitsX >> 16)
buf[*startIndex+3] = byte(bitsX >> 24)
buf[*startIndex+4] = byte(bitsY)
buf[*startIndex+5] = byte(bitsY >> 8)
buf[*startIndex+6] = byte(bitsY >> 16)
buf[*startIndex+7] = byte(bitsY >> 24)
*startIndex += alignmentPerVector
}
}
func WriteVec3SliceToByteBufWithAlignment(buf []byte, startIndex *int, alignmentPerVector int, vals []gglm.Vec3) {
assert.T(*startIndex+len(vals)*alignmentPerVector <= len(buf), "failed to write slice of gglm.Vec3 with custom alignment=%d to buffer because the buffer doesn't have enough space. Start index=%d, Buffer length=%d, but needs %d bytes free", alignmentPerVector, *startIndex, len(buf), len(vals)*alignmentPerVector)
for i := 0; i < len(vals); i++ {
bitsX := math.Float32bits(vals[i].X())
bitsY := math.Float32bits(vals[i].Y())
bitsZ := math.Float32bits(vals[i].Z())
buf[*startIndex] = byte(bitsX)
buf[*startIndex+1] = byte(bitsX >> 8)
buf[*startIndex+2] = byte(bitsX >> 16)
buf[*startIndex+3] = byte(bitsX >> 24)
buf[*startIndex+4] = byte(bitsY)
buf[*startIndex+5] = byte(bitsY >> 8)
buf[*startIndex+6] = byte(bitsY >> 16)
buf[*startIndex+7] = byte(bitsY >> 24)
buf[*startIndex+8] = byte(bitsZ)
buf[*startIndex+9] = byte(bitsZ >> 8)
buf[*startIndex+10] = byte(bitsZ >> 16)
buf[*startIndex+11] = byte(bitsZ >> 24)
*startIndex += alignmentPerVector
}
}
func WriteVec4SliceToByteBufWithAlignment(buf []byte, startIndex *int, alignmentPerVector int, vals []gglm.Vec4) {
assert.T(*startIndex+len(vals)*alignmentPerVector <= len(buf), "failed to write slice of gglm.Vec4 with custom alignment=%d to buffer because the buffer doesn't have enough space. Start index=%d, Buffer length=%d, but needs %d bytes free", alignmentPerVector, *startIndex, len(buf), len(vals)*alignmentPerVector)
for i := 0; i < len(vals); i++ {
bitsX := math.Float32bits(vals[i].X())
bitsY := math.Float32bits(vals[i].Y())
bitsZ := math.Float32bits(vals[i].Z())
bitsW := math.Float32bits(vals[i].W())
buf[*startIndex] = byte(bitsX)
buf[*startIndex+1] = byte(bitsX >> 8)
buf[*startIndex+2] = byte(bitsX >> 16)
buf[*startIndex+3] = byte(bitsX >> 24)
buf[*startIndex+4] = byte(bitsY)
buf[*startIndex+5] = byte(bitsY >> 8)
buf[*startIndex+6] = byte(bitsY >> 16)
buf[*startIndex+7] = byte(bitsY >> 24)
buf[*startIndex+8] = byte(bitsZ)
buf[*startIndex+9] = byte(bitsZ >> 8)
buf[*startIndex+10] = byte(bitsZ >> 16)
buf[*startIndex+11] = byte(bitsZ >> 24)
buf[*startIndex+12] = byte(bitsW)
buf[*startIndex+13] = byte(bitsW >> 8)
buf[*startIndex+14] = byte(bitsW >> 16)
buf[*startIndex+15] = byte(bitsW >> 24)
*startIndex += alignmentPerVector
}
}
func WriteMat2SliceToByteBufWithAlignment(buf []byte, startIndex *int, alignmentPerMatrix int, vals []gglm.Mat2) {
assert.T(*startIndex+len(vals)*alignmentPerMatrix <= len(buf), "failed to write slice of gglm.Mat2 with custom alignment=%d to buffer because the buffer doesn't have enough space. Start index=%d, Buffer length=%d, but needs %d bytes free", alignmentPerMatrix, *startIndex, len(buf), len(vals)*alignmentPerMatrix)
for i := 0; i < len(vals); i++ {
m := &vals[i]
WriteVec2SliceToByteBufWithAlignment(
buf,
startIndex,
16,
[]gglm.Vec2{
{Data: m.Data[0]},
{Data: m.Data[1]},
},
)
}
}
func WriteMat3SliceToByteBufWithAlignment(buf []byte, startIndex *int, alignmentPerMatrix int, vals []gglm.Mat3) {
assert.T(*startIndex+len(vals)*alignmentPerMatrix <= len(buf), "failed to write slice of gglm.Mat3 with custom alignment=%d to buffer because the buffer doesn't have enough space. Start index=%d, Buffer length=%d, but needs %d bytes free", alignmentPerMatrix, *startIndex, len(buf), len(vals)*alignmentPerMatrix)
for i := 0; i < len(vals); i++ {
m := &vals[i]
WriteVec3SliceToByteBufWithAlignment(
buf,
startIndex,
16,
[]gglm.Vec3{
{Data: m.Data[0]},
{Data: m.Data[1]},
{Data: m.Data[2]},
},
)
}
}
func WriteMat4SliceToByteBufWithAlignment(buf []byte, startIndex *int, alignmentPerMatrix int, vals []gglm.Mat4) {
assert.T(*startIndex+len(vals)*alignmentPerMatrix <= len(buf), "failed to write slice of gglm.Mat2 with custom alignment=%d to buffer because the buffer doesn't have enough space. Start index=%d, Buffer length=%d, but needs %d bytes free", alignmentPerMatrix, *startIndex, len(buf), len(vals)*alignmentPerMatrix)
for i := 0; i < len(vals); i++ {
m := &vals[i]
WriteVec4SliceToByteBufWithAlignment(
buf,
startIndex,
16,
[]gglm.Vec4{
{Data: m.Data[0]},
{Data: m.Data[1]},
{Data: m.Data[2]},
{Data: m.Data[3]},
},
)
}
}
func ReflectValueMatchesUniformBufferField(v reflect.Value, ubField *UniformBufferField) bool {
if v.Kind() == reflect.Pointer {
v = v.Elem()
}
switch ubField.Type {
case DataTypeUint32:
t := v.Type()
return t.Name() == "uint32"
case DataTypeFloat32:
t := v.Type()
return t.Name() == "float32"
case DataTypeInt32:
t := v.Type()
return t.Name() == "int32"
case DataTypeVec2:
_, ok := v.Interface().(gglm.Vec2)
return ok
case DataTypeVec3:
_, ok := v.Interface().(gglm.Vec3)
return ok
case DataTypeVec4:
_, ok := v.Interface().(gglm.Vec4)
return ok
case DataTypeMat2:
_, ok := v.Interface().(gglm.Mat2)
return ok
case DataTypeMat3:
_, ok := v.Interface().(gglm.Mat3)
return ok
case DataTypeMat4:
_, ok := v.Interface().(gglm.Mat4)
return ok
default:
assert.T(false, "Unknown uniform buffer data type passed. DataType '%d'", ubField.Type)
return false
}
}
func NewUniformBuffer(fields []UniformBufferFieldInput) UniformBuffer {
ub := UniformBuffer{}
ub.Size = addUniformBufferFieldsToArray(0, &ub.Fields, fields)
gl.GenBuffers(1, &ub.Id)
if ub.Id == 0 {
logging.ErrLog.Panicln("Failed to create OpenGL buffer for a uniform buffer")
}
ub.Bind()
gl.BufferData(gl.UNIFORM_BUFFER, int(ub.Size), gl.Ptr(nil), gl.STATIC_DRAW)
ub.UnBind()
return ub
}

View File

@ -55,7 +55,7 @@ func NewVertexBuffer(layout ...Element) VertexBuffer {
gl.GenBuffers(1, &vb.Id)
if vb.Id == 0 {
logging.ErrLog.Println("Failed to create OpenGL buffer")
logging.ErrLog.Panicln("Failed to create OpenGL buffer")
}
vb.SetLayout(layout...)

397
main.go
View File

@ -3,6 +3,7 @@ package main
import (
"fmt"
"os"
"reflect"
"runtime"
"runtime/pprof"
"strconv"
@ -40,14 +41,14 @@ import (
- HDR ✅
- Fix bad point light acne ✅
- UBO support
- Cascaded shadow mapping
- Skeletal animations
- Cascaded shadow mapping
- In some cases we DO want input even when captured by UI. We need two systems within input package, one filtered and one not✅
- (?) Support OpenGL 4.1 and 4.6, and default to 4.6
- Proper model loading (i.e. load model by reading all its meshes, textures, and so on together)
- Renderer batching
- Scene graph
- Separate engine loop from rendering loop? or leave it to the user?
- Abstract keys enum away from sdl?
- (?) Separate engine loop from rendering loop
- Frustum culling
- Proper Asset loading system
- Material system editor with fields automatically extracted from the shader
@ -207,17 +208,38 @@ func (s *SpotLight) OuterCutoffCos() float32 {
return gglm.Cos32(s.OuterCutoffRad)
}
type GlobalMatricesUboData struct {
CamPos gglm.Vec3
ProjViewMat gglm.Mat4
}
type DirLightUboData struct {
Dir gglm.Vec3
DiffuseColor gglm.Vec3
SpecularColor gglm.Vec3
Shadowmap int32
}
type LightsUboData struct {
DirLight DirLightUboData
}
const (
UNSCALED_WINDOW_WIDTH = 1280
UNSCALED_WINDOW_HEIGHT = 720
PROFILE_CPU = true
PROFILE_MEM = true
PROFILE_CPU = false
PROFILE_MEM = false
FRAME_TIME_MS_SAMPLES = 10000
)
var (
globalMatricesUboData GlobalMatricesUboData
globalMatricesUbo buffers.UniformBuffer
lightsUboData LightsUboData
lightsUbo buffers.UniformBuffer
frameTimesMsIndex int = 0
frameTimesMs []float32 = make([]float32, 0, FRAME_TIME_MS_SAMPLES)
@ -568,10 +590,7 @@ func (g *Game) Init() {
whiteMat.SetUnifInt32("material.emission", int32(materials.TextureSlot_Emission))
whiteMat.SetUnifVec3("ambientColor", &ambientColor)
whiteMat.SetUnifFloat32("material.shininess", whiteMat.Shininess)
whiteMat.SetUnifVec3("dirLight.dir", &dirLight.Dir)
whiteMat.SetUnifVec3("dirLight.diffuseColor", &dirLight.DiffuseColor)
whiteMat.SetUnifVec3("dirLight.specularColor", &dirLight.SpecularColor)
whiteMat.SetUnifInt32("dirLight.shadowMap", int32(materials.TextureSlot_ShadowMap1))
whiteMat.SetUnifInt32("dirLightShadowMap", int32(materials.TextureSlot_ShadowMap1))
whiteMat.SetUnifInt32("pointLightCubeShadowMaps", int32(materials.TextureSlot_Cubemap_Array))
whiteMat.SetUnifInt32("spotLightShadowMaps", int32(materials.TextureSlot_ShadowMap_Array1))
@ -586,10 +605,7 @@ func (g *Game) Init() {
containerMat.SetUnifInt32("material.emission", int32(materials.TextureSlot_Emission))
containerMat.SetUnifVec3("ambientColor", &ambientColor)
containerMat.SetUnifFloat32("material.shininess", containerMat.Shininess)
containerMat.SetUnifVec3("dirLight.dir", &dirLight.Dir)
containerMat.SetUnifVec3("dirLight.diffuseColor", &dirLight.DiffuseColor)
containerMat.SetUnifVec3("dirLight.specularColor", &dirLight.SpecularColor)
containerMat.SetUnifInt32("dirLight.shadowMap", int32(materials.TextureSlot_ShadowMap1))
containerMat.SetUnifInt32("dirLightShadowMap", int32(materials.TextureSlot_ShadowMap1))
containerMat.SetUnifInt32("pointLightCubeShadowMaps", int32(materials.TextureSlot_Cubemap_Array))
containerMat.SetUnifInt32("spotLightShadowMaps", int32(materials.TextureSlot_ShadowMap_Array1))
@ -604,10 +620,7 @@ func (g *Game) Init() {
groundMat.SetUnifInt32("material.emission", int32(materials.TextureSlot_Emission))
groundMat.SetUnifVec3("ambientColor", &ambientColor)
groundMat.SetUnifFloat32("material.shininess", groundMat.Shininess)
groundMat.SetUnifVec3("dirLight.dir", &dirLight.Dir)
groundMat.SetUnifVec3("dirLight.diffuseColor", &dirLight.DiffuseColor)
groundMat.SetUnifVec3("dirLight.specularColor", &dirLight.SpecularColor)
groundMat.SetUnifInt32("dirLight.shadowMap", int32(materials.TextureSlot_ShadowMap1))
groundMat.SetUnifInt32("dirLightShadowMap", int32(materials.TextureSlot_ShadowMap1))
groundMat.SetUnifInt32("pointLightCubeShadowMaps", int32(materials.TextureSlot_Cubemap_Array))
groundMat.SetUnifInt32("spotLightShadowMaps", int32(materials.TextureSlot_ShadowMap_Array1))
@ -621,10 +634,7 @@ func (g *Game) Init() {
palleteMat.SetUnifInt32("material.emission", int32(materials.TextureSlot_Emission))
palleteMat.SetUnifVec3("ambientColor", &ambientColor)
palleteMat.SetUnifFloat32("material.shininess", palleteMat.Shininess)
palleteMat.SetUnifVec3("dirLight.dir", &dirLight.Dir)
palleteMat.SetUnifVec3("dirLight.diffuseColor", &dirLight.DiffuseColor)
palleteMat.SetUnifVec3("dirLight.specularColor", &dirLight.SpecularColor)
palleteMat.SetUnifInt32("dirLight.shadowMap", int32(materials.TextureSlot_ShadowMap1))
palleteMat.SetUnifInt32("dirLightShadowMap", int32(materials.TextureSlot_ShadowMap1))
palleteMat.SetUnifInt32("pointLightCubeShadowMaps", int32(materials.TextureSlot_Cubemap_Array))
palleteMat.SetUnifInt32("spotLightShadowMaps", int32(materials.TextureSlot_ShadowMap_Array1))
@ -665,11 +675,282 @@ func (g *Game) Init() {
g.initFbos()
g.updateLights()
// Ubos
g.initUbos()
// testUbos()
// Initial camera update
cam.Update()
updateAllProjViewMats(cam.ProjMat, cam.ViewMat)
}
func (g *Game) initUbos() {
globalMatricesUbo = buffers.NewUniformBuffer(
[]buffers.UniformBufferFieldInput{
{Id: 0, Type: buffers.DataTypeVec3},
{Id: 1, Type: buffers.DataTypeMat4},
},
)
globalMatricesUbo.SetBindPoint(0)
groundMat.SetUniformBlockBindingPoint("GlobalMatrices", 0)
whiteMat.SetUniformBlockBindingPoint("GlobalMatrices", 0)
containerMat.SetUniformBlockBindingPoint("GlobalMatrices", 0)
palleteMat.SetUniformBlockBindingPoint("GlobalMatrices", 0)
lightsUbo = buffers.NewUniformBuffer(
[]buffers.UniformBufferFieldInput{
{Id: 0, Type: buffers.DataTypeStruct, Subfields: []buffers.UniformBufferFieldInput{
{Id: 1, Type: buffers.DataTypeVec3},
{Id: 2, Type: buffers.DataTypeVec3},
{Id: 3, Type: buffers.DataTypeVec3},
{Id: 4, Type: buffers.DataTypeInt32},
}},
},
)
lightsUbo.SetBindPoint(1)
groundMat.SetUniformBlockBindingPoint("Lights", 1)
whiteMat.SetUniformBlockBindingPoint("Lights", 1)
containerMat.SetUniformBlockBindingPoint("Lights", 1)
palleteMat.SetUniformBlockBindingPoint("Lights", 1)
}
func testUbos() {
xx := []int{1, 2, 3, 4}
xx2 := [4]int{1, 2, 3, 4}
fmt.Printf("XX: %v; Kind: %v; Elem Type: %v\n", reflect.ValueOf(xx), reflect.ValueOf(xx).Type().Kind(), reflect.ValueOf(xx).Type().Elem().Kind())
fmt.Printf("XX: %v; Kind: %v; Elem Type: %v\n", reflect.ValueOf(xx2), reflect.ValueOf(xx).Kind(), reflect.ValueOf(xx).Type().Elem().Kind())
ubo := buffers.NewUniformBuffer([]buffers.UniformBufferFieldInput{
{Id: 0, Type: buffers.DataTypeFloat32}, // 04 00
{Id: 1, Type: buffers.DataTypeVec3}, // 16 16
{Id: 2, Type: buffers.DataTypeFloat32}, // 04 32
{Id: 3, Type: buffers.DataTypeMat2}, // 32 48
}) // Total size: 48+32 = 80
ubo.Bind()
println("!!!!!!!!!!!!! Id:", ubo.Id, "; Size:", ubo.Size)
fmt.Printf("%+v\n", ubo.Fields)
ubo.SetFloat32(0, 99)
ubo.SetFloat32(2, 199)
ubo.SetVec3(1, &gglm.Vec3{Data: [3]float32{33, 33, 33}})
ubo.SetMat2(3, &gglm.Mat2{Data: [2][2]float32{{1, 3}, {2, 4}}})
var v gglm.Vec3
var m2 gglm.Mat2
var x, x2 float32
gl.GetBufferSubData(gl.UNIFORM_BUFFER, 0, 4, gl.Ptr(&x))
gl.GetBufferSubData(gl.UNIFORM_BUFFER, 32, 4, gl.Ptr(&x2))
gl.GetBufferSubData(gl.UNIFORM_BUFFER, 16, 12, gl.Ptr(&v.Data[0]))
gl.GetBufferSubData(gl.UNIFORM_BUFFER, 48, 16, gl.Ptr(&m2.Data[0][0]))
fmt.Printf("x=%f; x2=%f; v3=%s; m2=%s\n", x, x2, v.String(), m2.String())
ubo.SetVec3(1, &gglm.Vec3{Data: [3]float32{-123, 33, 33}})
gl.GetBufferSubData(gl.UNIFORM_BUFFER, 16, 12, gl.Ptr(&v.Data[0]))
type TestUBO struct {
FirstF32 float32
V3 gglm.Vec3
SecondF32 float32
M2 gglm.Mat2
}
s := TestUBO{
FirstF32: 1.5,
V3: gglm.Vec3{Data: [3]float32{11, 22, 33}},
SecondF32: 9.5,
M2: gglm.Mat2{Data: [2][2]float32{{6, 8}, {7, 9}}},
}
ubo.SetStruct(s)
gl.GetBufferSubData(gl.UNIFORM_BUFFER, 0, 4, gl.Ptr(&x))
gl.GetBufferSubData(gl.UNIFORM_BUFFER, 32, 4, gl.Ptr(&x2))
gl.GetBufferSubData(gl.UNIFORM_BUFFER, 16, 12, gl.Ptr(&v.Data[0]))
gl.GetBufferSubData(gl.UNIFORM_BUFFER, 48, 16, gl.Ptr(&m2.Data[0][0]))
fmt.Printf("x=%f; x2=%f; v3=%s; m2=%s\n", x, x2, v.String(), m2.String())
//
// Ubo2
//
type TestUBO2 struct {
F32 float32
V3 gglm.Vec3
F32Slice []float32
I32 int32
I32Slice []int32
V3Slice []gglm.Vec3
V4Slice []gglm.Vec4
Mat2Slice []gglm.Mat2
Mat3Slice []gglm.Mat3
Mat4Slice []gglm.Mat4
}
s2 := TestUBO2{
F32: 1.5,
V3: gglm.Vec3{Data: [3]float32{11, 22, 33}},
F32Slice: []float32{-1, -2, -3, -4},
I32: 55,
I32Slice: []int32{41, 42, 43},
V3Slice: []gglm.Vec3{gglm.NewVec3(1.1, 1.2, 1.3), gglm.NewVec3(2.1, 2.2, 2.3)},
V4Slice: []gglm.Vec4{gglm.NewVec4(1.1, 1.2, 1.3, 1.4), gglm.NewVec4(2.1, 2.2, 2.3, 2.4)},
Mat2Slice: []gglm.Mat2{gglm.NewMat2Diag(1.1), gglm.NewMat2Diag(2.1)},
Mat3Slice: []gglm.Mat3{gglm.NewMat3Diag(3.1), gglm.NewMat3Diag(4.1)},
Mat4Slice: []gglm.Mat4{gglm.NewMat4Diag(5.1), gglm.NewMat4Diag(6.1)},
}
ubo2 := buffers.NewUniformBuffer([]buffers.UniformBufferFieldInput{
{Id: 0, Type: buffers.DataTypeFloat32},
{Id: 1, Type: buffers.DataTypeVec3},
{Id: 2, Type: buffers.DataTypeFloat32, Count: 4},
{Id: 3, Type: buffers.DataTypeInt32},
{Id: 4, Type: buffers.DataTypeInt32, Count: 3},
{Id: 5, Type: buffers.DataTypeVec3, Count: 2},
{Id: 6, Type: buffers.DataTypeVec4, Count: 2},
{Id: 7, Type: buffers.DataTypeMat2, Count: 2},
{Id: 8, Type: buffers.DataTypeMat3, Count: 2},
{Id: 9, Type: buffers.DataTypeMat4, Count: 2},
})
ubo2.Bind()
ubo2.SetStruct(s2)
var someInt32 int32
fArr := [4 * 4]float32{}
i32Arr := [3 * 4]int32{}
vec3Slice := [2 * 4]float32{}
vec4Slice := [2 * 4]float32{}
mat2Slice := [2 * 2 * 4]float32{}
mat3Slice := [2 * 3 * 4]float32{}
mat4Slice := [2 * 4 * 4]float32{}
gl.GetBufferSubData(gl.UNIFORM_BUFFER, 0, 4, gl.Ptr(&x))
gl.GetBufferSubData(gl.UNIFORM_BUFFER, 16, 12, gl.Ptr(&v.Data[0]))
gl.GetBufferSubData(gl.UNIFORM_BUFFER, 32, 16*4, gl.Ptr(&fArr[0]))
gl.GetBufferSubData(gl.UNIFORM_BUFFER, 32+16*4, 4, gl.Ptr(&someInt32))
gl.GetBufferSubData(gl.UNIFORM_BUFFER, 32+16*4+16, 16*3, gl.Ptr(&i32Arr[0]))
gl.GetBufferSubData(gl.UNIFORM_BUFFER, 32+16*4+16+16*3, 16*2, gl.Ptr(&vec3Slice[0]))
gl.GetBufferSubData(gl.UNIFORM_BUFFER, 32+16*4+16+16*3+16*2, 16*2, gl.Ptr(&vec4Slice[0]))
gl.GetBufferSubData(gl.UNIFORM_BUFFER, 32+16*4+16+16*3+16*2+16*2, 2*16*2, gl.Ptr(&mat2Slice[0]))
gl.GetBufferSubData(gl.UNIFORM_BUFFER, 32+16*4+16+16*3+16*2+16*2+2*16*2, 2*16*3, gl.Ptr(&mat3Slice[0]))
gl.GetBufferSubData(gl.UNIFORM_BUFFER, 32+16*4+16+16*3+16*2+16*2+2*16*2+2*16*3, 2*16*4, gl.Ptr(&mat4Slice[0]))
fmt.Printf("f32=%f; v3=%s; f32Slice=%v; i32=%d; i32Arr=%v; v3Slice=%v; v4Slice=%v; mat2Slice=%v; mat3Slice=%v; mat4Slice=%v\n", x, v.String(), fArr, someInt32, i32Arr, vec3Slice, vec4Slice, mat2Slice, mat3Slice, mat4Slice)
//
// Ubo3
//
type TestUBO3_Z struct {
V int32
}
type TestUBO3_Y struct {
V TestUBO3_Z
}
type TestUBO3_X struct {
V TestUBO3_Y
}
type TestUBO3_0 struct {
X int32
}
type TestUBO3_1 struct {
F32 float32
V3 gglm.Vec3
Zero TestUBO3_0
}
type TestUBO3_2 struct {
F32 float32
S TestUBO3_1
XX int32
Z2 TestUBO3_0
XX2 int32
Abcd TestUBO3_X
}
ubo3 := buffers.NewUniformBuffer([]buffers.UniformBufferFieldInput{
{Id: 0, Type: buffers.DataTypeFloat32}, // 04 00
{Id: 1, Type: buffers.DataTypeStruct, Subfields: []buffers.UniformBufferFieldInput{ // 00 16
{Id: 2, Type: buffers.DataTypeFloat32}, // 04 16
{Id: 3, Type: buffers.DataTypeVec3}, // 16 32
{Id: 4, Type: buffers.DataTypeStruct, Subfields: []buffers.UniformBufferFieldInput{ // 00 48
{Id: 5, Type: buffers.DataTypeInt32}, // 04 48
}},
}},
{Id: 6, Type: buffers.DataTypeInt32}, // 04 64
{Id: 7, Type: buffers.DataTypeStruct, Subfields: []buffers.UniformBufferFieldInput{ // 00 80
{Id: 8, Type: buffers.DataTypeInt32}, // 04 80
}},
{Id: 9, Type: buffers.DataTypeInt32}, // 04 96
{Id: 10, Type: buffers.DataTypeStruct, Subfields: []buffers.UniformBufferFieldInput{ // 00 112
{Id: 11, Type: buffers.DataTypeStruct, Subfields: []buffers.UniformBufferFieldInput{ // 00 112
{Id: 12, Type: buffers.DataTypeStruct, Subfields: []buffers.UniformBufferFieldInput{ // 00 112
{Id: 13, Type: buffers.DataTypeInt32}, // 04 112
}},
}},
}},
}) // 116
ubo3.Bind()
ubo3.SetBindPoint(2)
groundMat.SetUniformBlockBindingPoint("Test2", 2)
fmt.Printf("\n==UBO3 (id=%d)==\nSize=%d\nFields: %+v\n\n", ubo3.Id, ubo3.Size, ubo3.Fields)
s3 := TestUBO3_2{
F32: 76.1,
S: TestUBO3_1{
F32: 89.9,
V3: gglm.NewVec3(7.1, 7.2, 7.3),
Zero: TestUBO3_0{
X: 33,
},
},
XX: 41,
Z2: TestUBO3_0{
X: 8,
},
XX2: 321,
Abcd: TestUBO3_X{
V: TestUBO3_Y{
V: TestUBO3_Z{
V: 9911,
},
},
},
}
ubo3.SetStruct(s3)
ubo3F32 := float32(0.0)
ubo3SF32 := float32(0.0)
ubo3SV3 := gglm.Vec3{}
ubo3SZeroX := 0
ubo3Xx := 0
ubo3SZ2X := 0
ubo3Xx2 := 0
ubo3AbcdV := 0
gl.GetBufferSubData(gl.UNIFORM_BUFFER, 0, 4, gl.Ptr(&ubo3F32))
gl.GetBufferSubData(gl.UNIFORM_BUFFER, 16, 4, gl.Ptr(&ubo3SF32))
gl.GetBufferSubData(gl.UNIFORM_BUFFER, 32, 16, gl.Ptr(&ubo3SV3.Data[0]))
gl.GetBufferSubData(gl.UNIFORM_BUFFER, 48, 4, gl.Ptr(&ubo3SZeroX))
gl.GetBufferSubData(gl.UNIFORM_BUFFER, 64, 4, gl.Ptr(&ubo3Xx))
gl.GetBufferSubData(gl.UNIFORM_BUFFER, 64, 4, gl.Ptr(&ubo3Xx))
gl.GetBufferSubData(gl.UNIFORM_BUFFER, 80, 4, gl.Ptr(&ubo3SZ2X))
gl.GetBufferSubData(gl.UNIFORM_BUFFER, 96, 4, gl.Ptr(&ubo3Xx2))
gl.GetBufferSubData(gl.UNIFORM_BUFFER, 112, 4, gl.Ptr(&ubo3AbcdV))
fmt.Printf("ubo3_f32=%f\nubo3_s_f32=%f\nubo3_s_v3=%s\nubo3_s_zero_x=%d\nubo3_xx=%d\nubo3_z2_x=%d\nubo3_xx2=%d\nubo3_abcd_v=%d\n", ubo3F32, ubo3SF32, ubo3SV3.String(), ubo3SZeroX, ubo3Xx, ubo3SZ2X, ubo3Xx2, ubo3AbcdV)
}
func (g *Game) initFbos() {
// @TODO: Resize window sized fbos on window resize
@ -737,10 +1018,16 @@ func (g *Game) initFbos() {
func (g *Game) updateLights() {
// Directional light
whiteMat.ShadowMapTex1 = dirLightDepthMapFbo.Attachments[0].Id
containerMat.ShadowMapTex1 = dirLightDepthMapFbo.Attachments[0].Id
groundMat.ShadowMapTex1 = dirLightDepthMapFbo.Attachments[0].Id
palleteMat.ShadowMapTex1 = dirLightDepthMapFbo.Attachments[0].Id
lightsUboData.DirLight = DirLightUboData{
Dir: dirLight.Dir,
DiffuseColor: dirLight.DiffuseColor,
SpecularColor: dirLight.SpecularColor,
Shadowmap: int32(dirLightDepthMapFbo.Attachments[0].Id),
}
whiteMat.ShadowMapTex1 = uint32(lightsUboData.DirLight.Shadowmap)
containerMat.ShadowMapTex1 = uint32(lightsUboData.DirLight.Shadowmap)
groundMat.ShadowMapTex1 = uint32(lightsUboData.DirLight.Shadowmap)
palleteMat.ShadowMapTex1 = uint32(lightsUboData.DirLight.Shadowmap)
// Point lights
for i := 0; i < len(pointLights); i++ {
@ -846,6 +1133,9 @@ func (g *Game) updateLights() {
containerMat.ShadowMapTexArray1 = spotLightDepthMapFbo.Attachments[0].Id
groundMat.ShadowMapTexArray1 = spotLightDepthMapFbo.Attachments[0].Id
palleteMat.ShadowMapTexArray1 = spotLightDepthMapFbo.Attachments[0].Id
lightsUbo.Bind()
lightsUbo.SetStruct(lightsUboData)
}
func (g *Game) Update() {
@ -857,6 +1147,9 @@ func (g *Game) Update() {
g.updateCameraLookAround()
g.updateCameraPos()
globalMatricesUboData.CamPos = cam.Pos
updateAllProjViewMats(cam.ProjMat, cam.ViewMat)
g.showDebugWindow()
}
@ -906,6 +1199,11 @@ func (g *Game) showDebugWindow() {
imgui.Spacing()
//
// Lights
//
updateLights := false
// Ambient light
imgui.Text("Ambient Light")
@ -924,30 +1222,29 @@ func (g *Game) showDebugWindow() {
imgui.Checkbox("Render Directional Light Shadows", &renderDirLightShadows)
if imgui.DragFloat3("Direction", &dirLight.Dir.Data) {
whiteMat.SetUnifVec3("dirLight.dir", &dirLight.Dir)
containerMat.SetUnifVec3("dirLight.dir", &dirLight.Dir)
groundMat.SetUnifVec3("dirLight.dir", &dirLight.Dir)
palleteMat.SetUnifVec3("dirLight.dir", &dirLight.Dir)
updateLights = true
}
if imgui.ColorEdit3("Diffuse Color", &dirLight.DiffuseColor.Data) {
whiteMat.SetUnifVec3("dirLight.diffuseColor", &dirLight.DiffuseColor)
containerMat.SetUnifVec3("dirLight.diffuseColor", &dirLight.DiffuseColor)
groundMat.SetUnifVec3("dirLight.diffuseColor", &dirLight.DiffuseColor)
palleteMat.SetUnifVec3("dirLight.diffuseColor", &dirLight.DiffuseColor)
updateLights = true
}
if imgui.ColorEdit3("Specular Color", &dirLight.SpecularColor.Data) {
whiteMat.SetUnifVec3("dirLight.specularColor", &dirLight.SpecularColor)
containerMat.SetUnifVec3("dirLight.specularColor", &dirLight.SpecularColor)
groundMat.SetUnifVec3("dirLight.specularColor", &dirLight.SpecularColor)
palleteMat.SetUnifVec3("dirLight.specularColor", &dirLight.SpecularColor)
updateLights = true
}
imgui.DragFloat3("dPos", &dirLightPos.Data)
imgui.DragFloat("dSize", &dirLightSize)
imgui.DragFloat("dNear", &dirLightNear)
imgui.DragFloat("dFar", &dirLightFar)
if imgui.DragFloat3("dPos", &dirLightPos.Data) {
updateLights = true
}
if imgui.DragFloat("dSize", &dirLightSize) {
updateLights = true
}
if imgui.DragFloat("dNear", &dirLightNear) {
updateLights = true
}
if imgui.DragFloat("dFar", &dirLightFar) {
updateLights = true
}
imgui.Spacing()
@ -1129,6 +1426,10 @@ func (g *Game) showDebugWindow() {
imgui.EndListBox()
}
if updateLights {
g.updateLights()
}
// Demo fbo
imgui.Text("Demo Framebuffer")
imgui.Checkbox("Show FBO##0", &renderToDemoFbo)
@ -1176,8 +1477,6 @@ func (g *Game) updateCameraLookAround() {
}
cam.UpdateRotation(pitch, yaw)
updateAllProjViewMats(cam.ProjMat, cam.ViewMat)
}
func (g *Game) updateCameraPos() {
@ -1211,7 +1510,6 @@ func (g *Game) updateCameraPos() {
if update {
cam.Update()
updateAllProjViewMats(cam.ProjMat, cam.ViewMat)
}
}
@ -1226,10 +1524,8 @@ var (
func (g *Game) Render() {
whiteMat.SetUnifVec3("camPos", &cam.Pos)
containerMat.SetUnifVec3("camPos", &cam.Pos)
groundMat.SetUnifVec3("camPos", &cam.Pos)
palleteMat.SetUnifVec3("camPos", &cam.Pos)
globalMatricesUbo.Bind()
globalMatricesUbo.SetStruct(globalMatricesUboData)
rotatingCubeTrMat1.Rotate(rotatingCubeSpeedDeg1*gglm.Deg2Rad*timing.DT(), 0, 1, 0)
rotatingCubeTrMat2.Rotate(rotatingCubeSpeedDeg2*gglm.Deg2Rad*timing.DT(), 1, 1, 0)
@ -1482,12 +1778,9 @@ func (g *Game) DeInit() {
func updateAllProjViewMats(projMat, viewMat gglm.Mat4) {
projViewMat := *projMat.Clone().Mul(&viewMat)
globalMatricesUboData.ProjViewMat = projViewMat
unlitMat.SetUnifMat4("projViewMat", &projViewMat)
whiteMat.SetUnifMat4("projViewMat", &projViewMat)
containerMat.SetUnifMat4("projViewMat", &projViewMat)
groundMat.SetUnifMat4("projViewMat", &projViewMat)
palleteMat.SetUnifMat4("projViewMat", &projViewMat)
debugDepthMat.SetUnifMat4("projViewMat", &projViewMat)
// Update skybox projViewMat

View File

@ -124,6 +124,21 @@ func (m *Material) UnBind() {
gl.UseProgram(0)
}
func (m *Material) SetUniformBlockBindingPoint(uniformBlockName string, bindPointIndex uint32) {
nullStr := gl.Str(uniformBlockName + "\x00")
index := gl.GetUniformBlockIndex(m.ShaderProg.Id, nullStr)
assert.T(
index != gl.INVALID_INDEX,
"SetUniformBlockBindingPoint for material=%s (matId=%d; shaderId=%d) failed because the uniform block=%s wasn't found",
m.Name,
m.Id,
m.ShaderProg.Id,
uniformBlockName,
)
gl.UniformBlockBinding(m.ShaderProg.Id, index, bindPointIndex)
}
func (m *Material) GetAttribLoc(attribName string) int32 {
loc, ok := m.AttribLocs[attribName]

View File

@ -14,22 +14,14 @@ layout(location=3) in vec2 vertUV0In;
layout(location=4) in vec3 vertColorIn;
//
// Uniforms
// UBOs
//
uniform vec3 camPos;
uniform mat4 modelMat;
uniform mat3 normalMat;
uniform mat4 projViewMat;
uniform mat4 dirLightProjViewMat;
uniform mat4 spotLightProjViewMats[NUM_SPOT_LIGHTS];
struct DirLight {
vec3 dir;
vec3 diffuseColor;
vec3 specularColor;
sampler2D shadowMap;
};
uniform DirLight dirLight;
uniform sampler2D dirLightShadowMap;
struct PointLight {
vec3 pos;
@ -41,7 +33,6 @@ struct PointLight {
float nearPlane;
float farPlane;
};
uniform PointLight pointLights[NUM_POINT_LIGHTS];
struct SpotLight {
vec3 pos;
@ -51,7 +42,24 @@ struct SpotLight {
float innerCutoff;
float outerCutoff;
};
layout (std140) uniform GlobalMatrices {
vec3 camPos;
mat4 projViewMat;
};
layout (std140) uniform Lights {
DirLight dirLight;
};
//
// Uniforms
//
uniform PointLight pointLights[NUM_POINT_LIGHTS];
uniform SpotLight spotLights[NUM_SPOT_LIGHTS];
uniform mat4 modelMat;
uniform mat4 dirLightProjViewMat;
uniform mat4 spotLightProjViewMats[NUM_SPOT_LIGHTS];
//
// Outputs
@ -70,6 +78,16 @@ out vec3 tangentSpotLightPositions[NUM_SPOT_LIGHTS];
out vec3 tangentSpotLightDirections[NUM_SPOT_LIGHTS];
out vec3 tangentPointLightPositions[NUM_POINT_LIGHTS];
struct Test1 {
float ff;
vec3 v3;
};
layout (std140) uniform Test2 {
float f1;
Test1 s;
};
void main()
{
vertUV0 = vertUV0In;
@ -155,9 +173,8 @@ struct DirLight {
vec3 dir;
vec3 diffuseColor;
vec3 specularColor;
sampler2D shadowMap;
};
uniform DirLight dirLight;
uniform sampler2D dirLightShadowMap;
struct PointLight {
vec3 pos;
@ -183,6 +200,15 @@ struct SpotLight {
uniform SpotLight spotLights[NUM_SPOT_LIGHTS];
uniform sampler2DArray spotLightShadowMaps;
layout (std140) uniform GlobalMatrices {
vec3 camPos;
mat4 projViewMat;
};
layout (std140) uniform Lights {
DirLight dirLight;
};
uniform vec3 ambientColor = vec3(0.2, 0.2, 0.2);
//
@ -250,7 +276,7 @@ vec3 CalcDirLight()
vec3 finalSpecular = specularAmount * dirLight.specularColor * specularTexColor.rgb;
// Shadow
float shadow = CalcDirShadow(dirLight.shadowMap, lightDir);
float shadow = CalcDirShadow(dirLightShadowMap, lightDir);
return (finalDiffuse + finalSpecular) * (1 - shadow);
}