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 {
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
}
type UniformBufferField struct {
Id 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 {
@ -49,6 +55,9 @@ func (ub *UniformBuffer) addFields(fields []UniformBufferFieldInput) (totalSize
for i := 0; i < len(fields); i++ {
f := fields[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())
@ -61,17 +70,23 @@ func (ub *UniformBuffer) addFields(fields []UniformBufferFieldInput) (totalSize
//
// 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
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
if alignmentError != 0 {
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)
// Prepare aligned offset for the next field
alignedOffset = newField.AlignedOffset + uint16(f.Type.GlStd140BaseAlignment())
alignedOffset = newField.AlignedOffset + alignmentBoundary*f.Count
}
return uint32(alignedOffset)
@ -166,37 +181,62 @@ func (ub *UniformBuffer) SetStruct(inputStruct any) {
ubField := &ub.Fields[i]
valField := structVal.Field(i)
if valField.Kind() == reflect.Pointer {
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
writeIndex = int(ubField.AlignedOffset)
switch ubField.Type {
case DataTypeUint32:
t := valField.Type()
typeMatches = t.Name() == "uint32"
typeMatches = elementType.Name() == "uint32"
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:
t := valField.Type()
typeMatches = t.Name() == "float32"
typeMatches = elementType.Name() == "float32"
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:
t := valField.Type()
typeMatches = t.Name() == "int32"
typeMatches = elementType.Name() == "int32"
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:
@ -282,6 +322,23 @@ func Write32BitIntegerToByteBuf[T uint32 | int32](buf []byte, startIndex *int, v
*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))
@ -298,7 +355,7 @@ func WriteF32ToByteBuf(buf []byte, startIndex *int, val 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++ {
@ -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 {
if v.Kind() == reflect.Pointer {