What if we could have ubos with nested structs

This commit is contained in:
bloeys
2024-09-14 18:38:59 +04:00
parent 0d34e0fe6e
commit bbc8652292
3 changed files with 124 additions and 67 deletions

View File

@ -50,6 +50,10 @@ func (ub *UniformBuffer) UnBind() {
gl.BindBuffer(gl.UNIFORM_BUFFER, 0) 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) { func addUniformBufferFieldsToArray(startAlignedOffset uint16, arrayToAddTo *[]UniformBufferField, fieldsToAdd []UniformBufferFieldInput) (totalSize uint32) {
if len(fieldsToAdd) == 0 { if len(fieldsToAdd) == 0 {
@ -113,7 +117,7 @@ func addUniformBufferFieldsToArray(startAlignedOffset uint16, arrayToAddTo *[]Un
if f.Type == DataTypeStruct { if f.Type == DataTypeStruct {
subfieldsAlignedOffset := uint16(addUniformBufferFieldsToArray(alignedOffset, arrayToAddTo, f.Subfields)) subfieldsAlignedOffset := uint16(addUniformBufferFieldsToArray(startAlignedOffset+alignedOffset, arrayToAddTo, f.Subfields))
// Pad structs to 16 byte boundary // Pad structs to 16 byte boundary
subfieldsAlignmentError := subfieldsAlignedOffset % 16 subfieldsAlignmentError := subfieldsAlignedOffset % 16
@ -199,6 +203,14 @@ func (ub *UniformBuffer) SetMat4(fieldId uint16, val *gglm.Mat4) {
} }
func (ub *UniformBuffer) SetStruct(inputStruct any) { func (ub *UniformBuffer) SetStruct(inputStruct any) {
setStruct(ub.Fields, make([]byte, ub.Size), inputStruct, 1000_000)
}
func setStruct(fields []UniformBufferField, buf []byte, inputStruct any, maxFieldsToConsume int) (bytesWritten, fieldsConsumed int) {
if len(fields) == 0 {
return
}
if inputStruct == nil { if inputStruct == nil {
logging.ErrLog.Panicf("UniformBuffer.SetStruct called with a value that is nil") logging.ErrLog.Panicf("UniformBuffer.SetStruct called with a value that is nil")
@ -209,16 +221,15 @@ func (ub *UniformBuffer) SetStruct(inputStruct any) {
logging.ErrLog.Panicf("UniformBuffer.SetStruct called with a value that is not a struct. Val=%v\n", inputStruct) logging.ErrLog.Panicf("UniformBuffer.SetStruct called with a value that is not a struct. Val=%v\n", inputStruct)
} }
if structVal.NumField() != len(ub.Fields) { structFieldIndex := 0
logging.ErrLog.Panicf("struct fields must match uniform buffer fields, but uniform buffer contains %d fields, while the passed struct has %d fields\n", len(ub.Fields), structVal.NumField()) // structFieldCount := structVal.NumField()
} for fieldIndex := 0; fieldIndex < len(fields) && fieldIndex < maxFieldsToConsume; fieldIndex++ {
writeIndex := 0 ubField := &fields[fieldIndex]
buf := make([]byte, ub.Size) valField := structVal.Field(structFieldIndex)
for i := 0; i < len(ub.Fields); i++ {
ubField := &ub.Fields[i] fieldsConsumed++
valField := structVal.Field(i) structFieldIndex++
kind := valField.Kind() kind := valField.Kind()
if kind == reflect.Pointer { if kind == reflect.Pointer {
@ -238,7 +249,7 @@ func (ub *UniformBuffer) SetStruct(inputStruct any) {
} }
typeMatches := false typeMatches := false
writeIndex = int(ubField.AlignedOffset) bytesWritten = int(ubField.AlignedOffset)
switch ubField.Type { switch ubField.Type {
@ -248,9 +259,9 @@ func (ub *UniformBuffer) SetStruct(inputStruct any) {
if typeMatches { if typeMatches {
if isArray { if isArray {
Write32BitIntegerSliceToByteBufWithAlignment(buf, &writeIndex, 16, valField.Slice(0, valField.Len()).Interface().([]uint32)) Write32BitIntegerSliceToByteBufWithAlignment(buf, &bytesWritten, 16, valField.Slice(0, valField.Len()).Interface().([]uint32))
} else { } else {
Write32BitIntegerToByteBuf(buf, &writeIndex, uint32(valField.Uint())) Write32BitIntegerToByteBuf(buf, &bytesWritten, uint32(valField.Uint()))
} }
} }
@ -260,9 +271,9 @@ func (ub *UniformBuffer) SetStruct(inputStruct any) {
if typeMatches { if typeMatches {
if isArray { if isArray {
WriteF32SliceToByteBufWithAlignment(buf, &writeIndex, 16, valField.Slice(0, valField.Len()).Interface().([]float32)) WriteF32SliceToByteBufWithAlignment(buf, &bytesWritten, 16, valField.Slice(0, valField.Len()).Interface().([]float32))
} else { } else {
WriteF32ToByteBuf(buf, &writeIndex, float32(valField.Float())) WriteF32ToByteBuf(buf, &bytesWritten, float32(valField.Float()))
} }
} }
@ -272,9 +283,9 @@ func (ub *UniformBuffer) SetStruct(inputStruct any) {
if typeMatches { if typeMatches {
if isArray { if isArray {
Write32BitIntegerSliceToByteBufWithAlignment(buf, &writeIndex, 16, valField.Slice(0, valField.Len()).Interface().([]int32)) Write32BitIntegerSliceToByteBufWithAlignment(buf, &bytesWritten, 16, valField.Slice(0, valField.Len()).Interface().([]int32))
} else { } else {
Write32BitIntegerToByteBuf(buf, &writeIndex, uint32(valField.Int())) Write32BitIntegerToByteBuf(buf, &bytesWritten, uint32(valField.Int()))
} }
} }
@ -285,10 +296,10 @@ func (ub *UniformBuffer) SetStruct(inputStruct any) {
if typeMatches { if typeMatches {
if isArray { if isArray {
WriteVec2SliceToByteBufWithAlignment(buf, &writeIndex, 16, valField.Slice(0, valField.Len()).Interface().([]gglm.Vec2)) WriteVec2SliceToByteBufWithAlignment(buf, &bytesWritten, 16, valField.Slice(0, valField.Len()).Interface().([]gglm.Vec2))
} else { } else {
v2 := valField.Interface().(gglm.Vec2) v2 := valField.Interface().(gglm.Vec2)
WriteF32SliceToByteBuf(buf, &writeIndex, v2.Data[:]) WriteF32SliceToByteBuf(buf, &bytesWritten, v2.Data[:])
} }
} }
@ -299,10 +310,10 @@ func (ub *UniformBuffer) SetStruct(inputStruct any) {
if typeMatches { if typeMatches {
if isArray { if isArray {
WriteVec3SliceToByteBufWithAlignment(buf, &writeIndex, 16, valField.Slice(0, valField.Len()).Interface().([]gglm.Vec3)) WriteVec3SliceToByteBufWithAlignment(buf, &bytesWritten, 16, valField.Slice(0, valField.Len()).Interface().([]gglm.Vec3))
} else { } else {
v3 := valField.Interface().(gglm.Vec3) v3 := valField.Interface().(gglm.Vec3)
WriteF32SliceToByteBuf(buf, &writeIndex, v3.Data[:]) WriteF32SliceToByteBuf(buf, &bytesWritten, v3.Data[:])
} }
} }
@ -313,10 +324,10 @@ func (ub *UniformBuffer) SetStruct(inputStruct any) {
if typeMatches { if typeMatches {
if isArray { if isArray {
WriteVec4SliceToByteBufWithAlignment(buf, &writeIndex, 16, valField.Slice(0, valField.Len()).Interface().([]gglm.Vec4)) WriteVec4SliceToByteBufWithAlignment(buf, &bytesWritten, 16, valField.Slice(0, valField.Len()).Interface().([]gglm.Vec4))
} else { } else {
v3 := valField.Interface().(gglm.Vec4) v3 := valField.Interface().(gglm.Vec4)
WriteF32SliceToByteBuf(buf, &writeIndex, v3.Data[:]) WriteF32SliceToByteBuf(buf, &bytesWritten, v3.Data[:])
} }
} }
@ -328,11 +339,11 @@ func (ub *UniformBuffer) SetStruct(inputStruct any) {
if isArray { if isArray {
m2Arr := valField.Interface().([]gglm.Mat2) m2Arr := valField.Interface().([]gglm.Mat2)
WriteMat2SliceToByteBufWithAlignment(buf, &writeIndex, 16*2, m2Arr) WriteMat2SliceToByteBufWithAlignment(buf, &bytesWritten, 16*2, m2Arr)
} else { } else {
m := valField.Interface().(gglm.Mat2) m := valField.Interface().(gglm.Mat2)
WriteF32SliceToByteBuf(buf, &writeIndex, m.Data[0][:]) WriteF32SliceToByteBuf(buf, &bytesWritten, m.Data[0][:])
WriteF32SliceToByteBuf(buf, &writeIndex, m.Data[1][:]) WriteF32SliceToByteBuf(buf, &bytesWritten, m.Data[1][:])
} }
} }
@ -344,12 +355,12 @@ func (ub *UniformBuffer) SetStruct(inputStruct any) {
if isArray { if isArray {
m3Arr := valField.Interface().([]gglm.Mat3) m3Arr := valField.Interface().([]gglm.Mat3)
WriteMat3SliceToByteBufWithAlignment(buf, &writeIndex, 16*3, m3Arr) WriteMat3SliceToByteBufWithAlignment(buf, &bytesWritten, 16*3, m3Arr)
} else { } else {
m := valField.Interface().(gglm.Mat3) m := valField.Interface().(gglm.Mat3)
WriteF32SliceToByteBuf(buf, &writeIndex, m.Data[0][:]) WriteF32SliceToByteBuf(buf, &bytesWritten, m.Data[0][:])
WriteF32SliceToByteBuf(buf, &writeIndex, m.Data[1][:]) WriteF32SliceToByteBuf(buf, &bytesWritten, m.Data[1][:])
WriteF32SliceToByteBuf(buf, &writeIndex, m.Data[2][:]) WriteF32SliceToByteBuf(buf, &bytesWritten, m.Data[2][:])
} }
} }
@ -361,34 +372,44 @@ func (ub *UniformBuffer) SetStruct(inputStruct any) {
if isArray { if isArray {
m4Arr := valField.Interface().([]gglm.Mat4) m4Arr := valField.Interface().([]gglm.Mat4)
WriteMat4SliceToByteBufWithAlignment(buf, &writeIndex, 16*4, m4Arr) WriteMat4SliceToByteBufWithAlignment(buf, &bytesWritten, 16*4, m4Arr)
} else { } else {
m := valField.Interface().(gglm.Mat4) m := valField.Interface().(gglm.Mat4)
WriteF32SliceToByteBuf(buf, &writeIndex, m.Data[0][:]) WriteF32SliceToByteBuf(buf, &bytesWritten, m.Data[0][:])
WriteF32SliceToByteBuf(buf, &writeIndex, m.Data[1][:]) WriteF32SliceToByteBuf(buf, &bytesWritten, m.Data[1][:])
WriteF32SliceToByteBuf(buf, &writeIndex, m.Data[2][:]) WriteF32SliceToByteBuf(buf, &bytesWritten, m.Data[2][:])
WriteF32SliceToByteBuf(buf, &writeIndex, m.Data[3][:]) WriteF32SliceToByteBuf(buf, &bytesWritten, m.Data[3][:])
} }
} }
// @TODO: Probably can change it similar to addFields, where we send in a struct AND a field array case DataTypeStruct:
// and let the function operate on that instead of the globaly fields array typeMatches = kind == reflect.Struct
// case DataTypeStruct:
if typeMatches {
setStructBytesWritten, setStructFieldsConsumed := setStruct(fields[fieldIndex+1:], buf, valField.Interface(), valField.NumField())
bytesWritten += setStructBytesWritten
fieldIndex += setStructFieldsConsumed
fieldsConsumed += setStructFieldsConsumed
}
default: default:
assert.T(false, "Unknown uniform buffer data type passed. DataType '%d'", ubField.Type) assert.T(false, "Unknown uniform buffer data type passed. DataType '%d'", ubField.Type)
} }
if !typeMatches { 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 %s\n", i, ubField, valField.String()) 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 writeIndex == 0 { if bytesWritten == 0 {
return return 0, fieldsConsumed
} }
gl.BufferSubData(gl.UNIFORM_BUFFER, 0, writeIndex, gl.Ptr(&buf[0])) 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) { func Write32BitIntegerToByteBuf[T uint32 | int32](buf []byte, startIndex *int, val T) {
@ -673,6 +694,7 @@ func NewUniformBuffer(fields []UniformBufferFieldInput) UniformBuffer {
ub.Bind() ub.Bind()
gl.BufferData(gl.UNIFORM_BUFFER, int(ub.Size), gl.Ptr(nil), gl.STATIC_DRAW) gl.BufferData(gl.UNIFORM_BUFFER, int(ub.Size), gl.Ptr(nil), gl.STATIC_DRAW)
ub.UnBind()
return ub return ub
} }

68
main.go
View File

@ -213,8 +213,8 @@ const (
UNSCALED_WINDOW_WIDTH = 1280 UNSCALED_WINDOW_WIDTH = 1280
UNSCALED_WINDOW_HEIGHT = 720 UNSCALED_WINDOW_HEIGHT = 720
PROFILE_CPU = true PROFILE_CPU = false
PROFILE_MEM = true PROFILE_MEM = false
FRAME_TIME_MS_SAMPLES = 10000 FRAME_TIME_MS_SAMPLES = 10000
) )
@ -688,15 +688,14 @@ func testUbos() {
{Id: 2, Type: buffers.DataTypeFloat32}, // 04 32 {Id: 2, Type: buffers.DataTypeFloat32}, // 04 32
{Id: 3, Type: buffers.DataTypeMat2}, // 32 48 {Id: 3, Type: buffers.DataTypeMat2}, // 32 48
}) // Total size: 48+32 = 80 }) // Total size: 48+32 = 80
ubo.Bind()
println("!!!!!!!!!!!!! Id:", ubo.Id, "; Size:", ubo.Size) println("!!!!!!!!!!!!! Id:", ubo.Id, "; Size:", ubo.Size)
fmt.Printf("%+v\n", ubo.Fields) fmt.Printf("%+v\n", ubo.Fields)
ubo.Bind()
ubo.SetFloat32(0, 99) ubo.SetFloat32(0, 99)
ubo.SetFloat32(2, 199) ubo.SetFloat32(2, 199)
ubo.SetVec3(1, &gglm.Vec3{Data: [3]float32{33, 33, 33}}) ubo.SetVec3(1, &gglm.Vec3{Data: [3]float32{33, 33, 33}})
ubo.SetMat2(3, &gglm.Mat2{Data: [2][2]float32{{1, 3}, {2, 4}}}) ubo.SetMat2(3, &gglm.Mat2{Data: [2][2]float32{{1, 3}, {2, 4}}})
var v gglm.Vec3 var v gglm.Vec3
@ -776,6 +775,7 @@ func testUbos() {
{Id: 8, Type: buffers.DataTypeMat3, Count: 2}, {Id: 8, Type: buffers.DataTypeMat3, Count: 2},
{Id: 9, Type: buffers.DataTypeMat4, Count: 2}, {Id: 9, Type: buffers.DataTypeMat4, Count: 2},
}) })
ubo2.Bind()
ubo2.SetStruct(s2) ubo2.SetStruct(s2)
@ -803,46 +803,66 @@ func testUbos() {
// //
// Ubo3 // Ubo3
// //
type TestUBO3_0 struct {
X int32
}
type TestUBO3_1 struct { type TestUBO3_1 struct {
F32 float32 F32 float32
V3 gglm.Vec3 V3 gglm.Vec3
Zero TestUBO3_0
} }
type TestUBO3_2 struct { type TestUBO3_2 struct {
F32 float32 F32 float32
S TestUBO3_1 S TestUBO3_1
XX int32
} }
ubo3 := buffers.NewUniformBuffer([]buffers.UniformBufferFieldInput{ ubo3 := buffers.NewUniformBuffer([]buffers.UniformBufferFieldInput{
{Id: 0, Type: buffers.DataTypeFloat32}, // 04 00 {Id: 0, Type: buffers.DataTypeFloat32}, // 04 00
{Id: 1, Type: buffers.DataTypeStruct, Subfields: []buffers.UniformBufferFieldInput{ // 00 16 {Id: 1, Type: buffers.DataTypeStruct, Subfields: []buffers.UniformBufferFieldInput{ // 00 16
{Id: 2, Type: buffers.DataTypeFloat32}, // 04 20 {Id: 2, Type: buffers.DataTypeFloat32}, // 04 16
{Id: 3, Type: buffers.DataTypeVec3}, // 16 32 {Id: 3, Type: buffers.DataTypeVec3}, // 16 32
}}, // 32+16 = 48 {Id: 4, Type: buffers.DataTypeStruct, Subfields: []buffers.UniformBufferFieldInput{ // 00 48
}) {Id: 5, Type: buffers.DataTypeInt32}, // 04 48
}},
}},
{Id: 6, Type: buffers.DataTypeInt32}, // 04 64
}) // 68
ubo3.Bind()
fmt.Printf("\n==UBO3==\nSize=%d\nFields: %+v", ubo3.Size, ubo3.Fields) ubo3.SetBindPoint(2)
groundMat.SetUniformBlockBindingPoint("Test2", 2)
ubo3.SetFloat32(0, 11) fmt.Printf("\n==UBO3==\nSize=%d\nFields: %+v\n\n", ubo3.Size, ubo3.Fields)
ubo3.SetFloat32(2, 22)
ubo3.SetVec3(3, &gglm.Vec3{Data: [3]float32{33, 44, 55}})
// Bind the uniform block and the vertex buffer both to binding slot 2 s3 := TestUBO3_2{
gl.BindBufferBase(gl.UNIFORM_BUFFER, 2, ubo3.Id) 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,
}
name := "Test2\x00" ubo3.SetStruct(s3)
uniformBlockIndex := gl.GetUniformBlockIndex(groundMat.ShaderProg.Id, gl.Str(name))
gl.UniformBlockBinding(groundMat.ShaderProg.Id, uniformBlockIndex, 2)
// s3 := TestUBO3_2{ ubo3F32 := float32(0.0)
// F32: 76.1, ubo3SF32 := float32(0.0)
// S: TestUBO3_1{ ubo3SV3 := gglm.Vec3{}
// F32: 89.9, ubo3SZeroX := 0
// V3: gglm.NewVec3(7.1, 7.2, 7.3), ubo3Xx := 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))
// ubo3.SetStruct(s3) fmt.Printf("ubo3_f32=%f\nubo3_s_f32=%f\nubo3_s_v3=%s\nubo3_s_zero_x=%d\nubo3_xx=%d\n", ubo3F32, ubo3SF32, ubo3SV3.String(), ubo3SZeroX, ubo3Xx)
} }
func (g *Game) initFbos() { func (g *Game) initFbos() {

View File

@ -124,6 +124,21 @@ func (m *Material) UnBind() {
gl.UseProgram(0) 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 { func (m *Material) GetAttribLoc(attribName string) int32 {
loc, ok := m.AttribLocs[attribName] loc, ok := m.AttribLocs[attribName]