From ff7fe4e531d2abddc7a0ed6bef59703adf86e676 Mon Sep 17 00:00:00 2001 From: bloeys Date: Sun, 26 May 2024 12:55:58 +0400 Subject: [PATCH] Struct to ubo support --- buffers/uniform_buffer.go | 214 ++++++++++++++++++++++++++++++++++++++ main.go | 75 ++++++++----- 2 files changed, 263 insertions(+), 26 deletions(-) diff --git a/buffers/uniform_buffer.go b/buffers/uniform_buffer.go index 6046538..926292f 100755 --- a/buffers/uniform_buffer.go +++ b/buffers/uniform_buffer.go @@ -1,6 +1,9 @@ package buffers import ( + "math" + "reflect" + "github.com/bloeys/gglm/gglm" "github.com/bloeys/nmage/assert" "github.com/bloeys/nmage/logging" @@ -141,6 +144,217 @@ func (ub *UniformBuffer) SetMat4(fieldId uint16, val *gglm.Mat4) { gl.BufferSubData(gl.UNIFORM_BUFFER, int(f.AlignedOffset), 4*16, gl.Ptr(&val.Data[0][0])) } +func (ub *UniformBuffer) SetStruct(inputStruct any) { + + 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) + } + + if structVal.NumField() != len(ub.Fields) { + 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()) + } + + writeIndex := 0 + buf := make([]byte, ub.Size) + for i := 0; i < len(ub.Fields); i++ { + + ubField := &ub.Fields[i] + valField := structVal.Field(i) + + if valField.Kind() == reflect.Pointer { + valField = valField.Elem() + } + + typeMatches := false + writeIndex = int(ubField.AlignedOffset) + + switch ubField.Type { + + case DataTypeUint32: + t := valField.Type() + typeMatches = t.Name() == "uint32" + + if typeMatches { + Write32BitIntegerToByteBuf(buf, &writeIndex, uint32(valField.Uint())) + } + + case DataTypeFloat32: + t := valField.Type() + typeMatches = t.Name() == "float32" + + if typeMatches { + WriteF32ToByteBuf(buf, &writeIndex, float32(valField.Float())) + } + + case DataTypeInt32: + t := valField.Type() + typeMatches = t.Name() == "int32" + + if typeMatches { + Write32BitIntegerToByteBuf(buf, &writeIndex, uint32(valField.Int())) + } + + case DataTypeVec2: + + v2, ok := valField.Interface().(gglm.Vec2) + typeMatches = ok + + if typeMatches { + WriteF32SliceToByteBuf(buf, &writeIndex, v2.Data[:]) + } + + case DataTypeVec3: + v3, ok := valField.Interface().(gglm.Vec3) + typeMatches = ok + + if typeMatches { + WriteF32SliceToByteBuf(buf, &writeIndex, v3.Data[:]) + } + + case DataTypeVec4: + v4, ok := valField.Interface().(gglm.Vec4) + typeMatches = ok + + if typeMatches { + WriteF32SliceToByteBuf(buf, &writeIndex, v4.Data[:]) + } + + case DataTypeMat2: + m2, ok := valField.Interface().(gglm.Mat2) + typeMatches = ok + + if typeMatches { + WriteF32SliceToByteBuf(buf, &writeIndex, m2.Data[0][:]) + WriteF32SliceToByteBuf(buf, &writeIndex, m2.Data[1][:]) + } + + case DataTypeMat3: + m3, ok := valField.Interface().(gglm.Mat3) + typeMatches = ok + + if typeMatches { + WriteF32SliceToByteBuf(buf, &writeIndex, m3.Data[0][:]) + WriteF32SliceToByteBuf(buf, &writeIndex, m3.Data[1][:]) + WriteF32SliceToByteBuf(buf, &writeIndex, m3.Data[2][:]) + } + + case DataTypeMat4: + m4, ok := valField.Interface().(gglm.Mat4) + typeMatches = ok + + if typeMatches { + WriteF32SliceToByteBuf(buf, &writeIndex, m4.Data[0][:]) + WriteF32SliceToByteBuf(buf, &writeIndex, m4.Data[1][:]) + WriteF32SliceToByteBuf(buf, &writeIndex, m4.Data[2][:]) + WriteF32SliceToByteBuf(buf, &writeIndex, m4.Data[3][:]) + } + + 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 %s\n", i, ubField, valField.String()) + } + } + + if writeIndex == 0 { + return + } + + gl.BufferSubData(gl.UNIFORM_BUFFER, 0, writeIndex, gl.Ptr(&buf[0])) +} + +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 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+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 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{} diff --git a/main.go b/main.go index 941ffe1..e12a5e0 100755 --- a/main.go +++ b/main.go @@ -671,41 +671,64 @@ func (g *Game) Init() { updateAllProjViewMats(cam.ProjMat, cam.ViewMat) // Ubos - // testUbos() + testUbos() } -// func testUbos() { +func testUbos() { -// 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 := 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 -// println("!!!!!!!!!!!!! Id:", ubo.Id, "; Size:", ubo.Size) -// fmt.Printf("%+v\n", ubo.Fields) + println("!!!!!!!!!!!!! Id:", ubo.Id, "; Size:", ubo.Size) + fmt.Printf("%+v\n", ubo.Fields) -// ubo.Bind() -// ubo.SetFloat32(0, 99) -// ubo.SetFloat32(2, 199) -// ubo.SetVec3(1, &gglm.Vec3{Data: [3]float32{33, 33, 33}}) + ubo.Bind() + 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}}}) + 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])) + 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()) + 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])) -// } + 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()) +} func (g *Game) initFbos() {