mirror of
https://github.com/bloeys/nmage.git
synced 2025-12-29 05:18:21 +00:00
Scalar uniform array in uniform buffers
This commit is contained in:
@ -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 {
|
||||
|
||||
46
main.go
46
main.go
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"strconv"
|
||||
@ -676,6 +677,11 @@ func (g *Game) Init() {
|
||||
|
||||
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
|
||||
@ -728,6 +734,46 @@ func testUbos() {
|
||||
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
|
||||
}
|
||||
|
||||
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() {
|
||||
|
||||
Reference in New Issue
Block a user