mirror of
https://github.com/bloeys/nmage.git
synced 2025-12-29 13:28:20 +00:00
Scalar uniform array in uniform buffers
This commit is contained in:
@ -13,11 +13,17 @@ 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
|
||||||
|
// 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 ElementType
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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,38 +181,63 @@ 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 {
|
||||||
|
|
||||||
|
if isArray {
|
||||||
|
Write32BitIntegerSliceToByteBufWithAlignment(buf, &writeIndex, 16, valField.Slice(0, valField.Len()).Interface().([]uint32))
|
||||||
|
} else {
|
||||||
Write32BitIntegerToByteBuf(buf, &writeIndex, uint32(valField.Uint()))
|
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 {
|
||||||
|
|
||||||
|
if isArray {
|
||||||
|
WriteF32SliceToByteBufWithAlignment(buf, &writeIndex, 16, valField.Slice(0, valField.Len()).Interface().([]float32))
|
||||||
|
} else {
|
||||||
WriteF32ToByteBuf(buf, &writeIndex, float32(valField.Float()))
|
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 {
|
||||||
|
|
||||||
|
if isArray {
|
||||||
|
Write32BitIntegerSliceToByteBufWithAlignment(buf, &writeIndex, 16, valField.Slice(0, valField.Len()).Interface().([]int32))
|
||||||
|
} else {
|
||||||
Write32BitIntegerToByteBuf(buf, &writeIndex, uint32(valField.Int()))
|
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
46
main.go
@ -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() {
|
||||||
|
|||||||
Reference in New Issue
Block a user