Scalar uniform array in uniform buffers

This commit is contained in:
bloeys
2024-05-26 15:03:00 +04:00
parent ff7fe4e531
commit 79cb6805c4
2 changed files with 135 additions and 15 deletions

View File

@ -13,12 +13,18 @@ import (
type UniformBufferFieldInput struct { type UniformBufferFieldInput struct {
Id uint16 Id uint16
Type ElementType 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
} }
type UniformBufferField struct { type UniformBufferField struct {
Id uint16 Id uint16
AlignedOffset uint16 AlignedOffset 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
Type ElementType
} }
type UniformBuffer struct { type UniformBuffer struct {
@ -49,6 +55,9 @@ func (ub *UniformBuffer) addFields(fields []UniformBufferFieldInput) (totalSize
for i := 0; i < len(fields); i++ { for i := 0; i < len(fields); i++ {
f := fields[i] f := fields[i]
if f.Count == 0 {
f.Count = 1
}
existingFieldType, ok := fieldIdToTypeMap[f.Id] 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()) 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())
@ -61,17 +70,23 @@ func (ub *UniformBuffer) addFields(fields []UniformBufferFieldInput) (totalSize
// //
// To get the nearest boundary larger than the offset we can: // 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 // offset + (boundary - alignErr) == 100 + (16 - 4) == 112; 112 % 16 == 0, meaning its a boundary
alignmentBoundary := f.Type.GlStd140AlignmentBoundary() //
// 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 alignmentError := alignedOffset % alignmentBoundary
if alignmentError != 0 { if alignmentError != 0 {
alignedOffset += alignmentBoundary - alignmentError alignedOffset += alignmentBoundary - alignmentError
} }
newField := UniformBufferField{Id: f.Id, Type: f.Type, AlignedOffset: alignedOffset} newField := UniformBufferField{Id: f.Id, Type: f.Type, AlignedOffset: alignedOffset, Count: f.Count}
ub.Fields = append(ub.Fields, newField) ub.Fields = append(ub.Fields, newField)
// Prepare aligned offset for the next field // Prepare aligned offset for the next field
alignedOffset = newField.AlignedOffset + uint16(f.Type.GlStd140BaseAlignment()) alignedOffset = newField.AlignedOffset + alignmentBoundary*f.Count
} }
return uint32(alignedOffset) return uint32(alignedOffset)
@ -166,37 +181,62 @@ func (ub *UniformBuffer) SetStruct(inputStruct any) {
ubField := &ub.Fields[i] ubField := &ub.Fields[i]
valField := structVal.Field(i) valField := structVal.Field(i)
if valField.Kind() == reflect.Pointer { kind := valField.Kind()
if kind == reflect.Pointer {
valField = valField.Elem() 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 typeMatches := false
writeIndex = int(ubField.AlignedOffset) writeIndex = int(ubField.AlignedOffset)
switch ubField.Type { switch ubField.Type {
case DataTypeUint32: case DataTypeUint32:
t := valField.Type()
typeMatches = t.Name() == "uint32"
typeMatches = elementType.Name() == "uint32"
if typeMatches { if typeMatches {
Write32BitIntegerToByteBuf(buf, &writeIndex, uint32(valField.Uint()))
if isArray {
Write32BitIntegerSliceToByteBufWithAlignment(buf, &writeIndex, 16, valField.Slice(0, valField.Len()).Interface().([]uint32))
} else {
Write32BitIntegerToByteBuf(buf, &writeIndex, uint32(valField.Uint()))
}
} }
case DataTypeFloat32: case DataTypeFloat32:
t := valField.Type()
typeMatches = t.Name() == "float32"
typeMatches = elementType.Name() == "float32"
if typeMatches { if typeMatches {
WriteF32ToByteBuf(buf, &writeIndex, float32(valField.Float()))
if isArray {
WriteF32SliceToByteBufWithAlignment(buf, &writeIndex, 16, valField.Slice(0, valField.Len()).Interface().([]float32))
} else {
WriteF32ToByteBuf(buf, &writeIndex, float32(valField.Float()))
}
} }
case DataTypeInt32: case DataTypeInt32:
t := valField.Type()
typeMatches = t.Name() == "int32"
typeMatches = elementType.Name() == "int32"
if typeMatches { if typeMatches {
Write32BitIntegerToByteBuf(buf, &writeIndex, uint32(valField.Int()))
if isArray {
Write32BitIntegerSliceToByteBufWithAlignment(buf, &writeIndex, 16, valField.Slice(0, valField.Len()).Interface().([]int32))
} else {
Write32BitIntegerToByteBuf(buf, &writeIndex, uint32(valField.Int()))
}
} }
case DataTypeVec2: case DataTypeVec2:
@ -282,6 +322,23 @@ func Write32BitIntegerToByteBuf[T uint32 | int32](buf []byte, startIndex *int, v
*startIndex += 4 *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) { 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)) 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))
@ -298,7 +355,7 @@ func WriteF32ToByteBuf(buf []byte, startIndex *int, val float32) {
func WriteF32SliceToByteBuf(buf []byte, startIndex *int, vals []float32) { 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) 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++ { for i := 0; i < len(vals); i++ {
@ -313,6 +370,23 @@ func WriteF32SliceToByteBuf(buf []byte, startIndex *int, vals []float32) {
} }
} }
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 ReflectValueMatchesUniformBufferField(v reflect.Value, ubField *UniformBufferField) bool { func ReflectValueMatchesUniformBufferField(v reflect.Value, ubField *UniformBufferField) bool {
if v.Kind() == reflect.Pointer { if v.Kind() == reflect.Pointer {

46
main.go
View File

@ -3,6 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
"reflect"
"runtime" "runtime"
"runtime/pprof" "runtime/pprof"
"strconv" "strconv"
@ -676,6 +677,11 @@ func (g *Game) Init() {
func testUbos() { 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{ ubo := buffers.NewUniformBuffer([]buffers.UniformBufferFieldInput{
{Id: 0, Type: buffers.DataTypeFloat32}, // 04 00 {Id: 0, Type: buffers.DataTypeFloat32}, // 04 00
{Id: 1, Type: buffers.DataTypeVec3}, // 16 16 {Id: 1, Type: buffers.DataTypeVec3}, // 16 16
@ -728,6 +734,46 @@ func testUbos() {
gl.GetBufferSubData(gl.UNIFORM_BUFFER, 48, 16, gl.Ptr(&m2.Data[0][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())
//
// Ubo2
//
type TestUBO2 struct {
F32 float32
V3 gglm.Vec3
F32Slice []float32
I32 int32
I32Slice []int32
}
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, 44, 45},
}
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: 5},
})
ubo2.SetStruct(s2)
var someInt32 int32
fArr := [4 * 4]float32{}
i32Arr := [5 * 4]int32{}
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*5, gl.Ptr(&i32Arr[0]))
fmt.Printf("f32=%f; v3=%s; f32Slice=%v; i32=%d; i32Arr=%v\n", x, v.String(), fArr, someInt32, i32Arr)
} }
func (g *Game) initFbos() { func (g *Game) initFbos() {