Skip to content

Commit

Permalink
reduce modeler.Read helpers allocations
Browse files Browse the repository at this point in the history
  • Loading branch information
qmuntal committed Aug 30, 2024
1 parent f1adfc5 commit 2344d28
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 63 deletions.
146 changes: 87 additions & 59 deletions binary/slice.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,144 +9,156 @@ import (
"github.com/qmuntal/gltf"
)

func castSlice(c gltf.ComponentType, t gltf.AccessorType, v []byte) any {
func reslice[T any](v []byte, length, rem int) []T {
var ptr unsafe.Pointer
if len(v) != 0 {
ptr = unsafe.Pointer(&v[0])
}
s := unsafe.Slice((*T)(unsafe.Pointer(ptr)), length)
if rem > 0 {
return append(s, make([]T, rem)...)
}
return s[:length]
}

func castSlice(c gltf.ComponentType, t gltf.AccessorType, count int, v []byte) any {
l := len(v) / (c.ByteSize() * t.Components())
var rem int
if count < l {
l = count
} else if count > l {
l = count
rem = count - l
}

switch c {
case gltf.ComponentUbyte:
switch t {
case gltf.AccessorScalar:
return v
return reslice[uint8](v, l, rem)
case gltf.AccessorVec2:
return unsafe.Slice((*[2]uint8)(ptr), len(v)/2)
return reslice[[2]uint8](v, l, rem)
case gltf.AccessorVec3:
return unsafe.Slice((*[3]uint8)(ptr), len(v)/3)
return reslice[[3]uint8](v, l, rem)
case gltf.AccessorVec4:
return unsafe.Slice((*[4]uint8)(ptr), len(v)/4)
return reslice[[4]uint8](v, l, rem)
case gltf.AccessorMat2:
return unsafe.Slice((*[2][2]uint8)(ptr), len(v)/4)
return reslice[[2][2]uint8](v, l, rem)
case gltf.AccessorMat3:
return unsafe.Slice((*[3][3]uint8)(ptr), len(v)/9)
return reslice[[3][3]uint8](v, l, rem)
case gltf.AccessorMat4:
return unsafe.Slice((*[4][4]uint8)(ptr), len(v)/16)
return reslice[[4][4]uint8](v, l, rem)
}
case gltf.ComponentByte:
switch t {
case gltf.AccessorScalar:
return unsafe.Slice((*int8)(ptr), len(v))
return reslice[int8](v, l, rem)
case gltf.AccessorVec2:
return unsafe.Slice((*[2]int8)(ptr), len(v)/2)
return reslice[[2]int8](v, l, rem)
case gltf.AccessorVec3:
return unsafe.Slice((*[3]int8)(ptr), len(v)/3)
return reslice[[3]int8](v, l, rem)
case gltf.AccessorVec4:
return unsafe.Slice((*[4]int8)(ptr), len(v)/4)
return reslice[[4]int8](v, l, rem)
case gltf.AccessorMat2:
return unsafe.Slice((*[2][2]int8)(ptr), len(v)/4)
return reslice[[2][2]int8](v, l, rem)
case gltf.AccessorMat3:
return unsafe.Slice((*[3][3]int8)(ptr), len(v)/9)
return reslice[[3][3]int8](v, l, rem)
case gltf.AccessorMat4:
return unsafe.Slice((*[4][4]int8)(ptr), len(v)/16)
return reslice[[4][4]int8](v, l, rem)
}
case gltf.ComponentUshort:
switch t {
case gltf.AccessorScalar:
return unsafe.Slice((*uint16)(ptr), len(v)/2)
return reslice[uint16](v, l, rem)
case gltf.AccessorVec2:
return unsafe.Slice((*[2]uint16)(ptr), len(v)/4)
return reslice[[2]uint16](v, l, rem)
case gltf.AccessorVec3:
return unsafe.Slice((*[3]uint16)(ptr), len(v)/6)
return reslice[[3]uint16](v, l, rem)
case gltf.AccessorVec4:
return unsafe.Slice((*[4]uint16)(ptr), len(v)/8)
return reslice[[4]uint16](v, l, rem)
case gltf.AccessorMat2:
return unsafe.Slice((*[2][2]uint16)(ptr), len(v)/8)
return reslice[[2][2]uint16](v, l, rem)
case gltf.AccessorMat3:
return unsafe.Slice((*[3][3]uint16)(ptr), len(v)/18)
return reslice[[3][3]uint16](v, l, rem)
case gltf.AccessorMat4:
return unsafe.Slice((*[4][4]uint16)(ptr), len(v)/32)
return reslice[[4][4]uint16](v, l, rem)
}
case gltf.ComponentShort:
switch t {
case gltf.AccessorScalar:
return unsafe.Slice((*int16)(ptr), len(v)/2)
return reslice[int16](v, l, rem)
case gltf.AccessorVec2:
return unsafe.Slice((*[2]int16)(ptr), len(v)/4)
return reslice[[2]int16](v, l, rem)
case gltf.AccessorVec3:
return unsafe.Slice((*[3]int16)(ptr), len(v)/6)
return reslice[[3]int16](v, l, rem)
case gltf.AccessorVec4:
return unsafe.Slice((*[4]int16)(ptr), len(v)/8)
return reslice[[4]int16](v, l, rem)
case gltf.AccessorMat2:
return unsafe.Slice((*[2][2]int16)(ptr), len(v)/8)
return reslice[[2][2]int16](v, l, rem)
case gltf.AccessorMat3:
return unsafe.Slice((*[3][3]int16)(ptr), len(v)/18)
return reslice[[3][3]int16](v, l, rem)
case gltf.AccessorMat4:
return unsafe.Slice((*[4][4]int16)(ptr), len(v)/32)
return reslice[[4][4]int16](v, l, rem)
}
case gltf.ComponentUint:
switch t {
case gltf.AccessorScalar:
return unsafe.Slice((*uint32)(ptr), len(v)/4)
return reslice[uint32](v, l, rem)
case gltf.AccessorVec2:
return unsafe.Slice((*[2]uint32)(ptr), len(v)/8)
return reslice[[2]uint32](v, l, rem)
case gltf.AccessorVec3:
return unsafe.Slice((*[3]uint32)(ptr), len(v)/12)
return reslice[[3]uint32](v, l, rem)
case gltf.AccessorVec4:
return unsafe.Slice((*[4]uint32)(ptr), len(v)/16)
return reslice[[4]uint32](v, l, rem)
case gltf.AccessorMat2:
return unsafe.Slice((*[2][2]uint32)(ptr), len(v)/16)
return reslice[[2][2]uint32](v, l, rem)
case gltf.AccessorMat3:
return unsafe.Slice((*[3][3]uint32)(ptr), len(v)/36)

case gltf.AccessorMat4:
return unsafe.Slice((*[4][4]uint32)(ptr), len(v)/64)
return reslice[[4][4]uint32](v, l, rem)
}
case gltf.ComponentFloat:
switch t {
case gltf.AccessorScalar:
return unsafe.Slice((*float32)(ptr), len(v)/4)
return reslice[float32](v, l, rem)
case gltf.AccessorVec2:
return unsafe.Slice((*[2]float32)(ptr), len(v)/8)
return reslice[[2]float32](v, l, rem)
case gltf.AccessorVec3:
return unsafe.Slice((*[3]float32)(ptr), len(v)/12)
return reslice[[3]float32](v, l, rem)
case gltf.AccessorVec4:
return unsafe.Slice((*[4]float32)(ptr), len(v)/16)
return reslice[[4]float32](v, l, rem)
case gltf.AccessorMat2:
return unsafe.Slice((*[2][2]float32)(ptr), len(v)/16)
return reslice[[2][2]float32](v, l, rem)
case gltf.AccessorMat3:
return unsafe.Slice((*[3][3]float32)(ptr), len(v)/36)
return reslice[[3][3]float32](v, l, rem)
case gltf.AccessorMat4:
return unsafe.Slice((*[4][4]float32)(ptr), len(v)/64)
return reslice[[4][4]float32](v, l, rem)
}
}
return nil
panic(fmt.Errorf("gltf: unsupported component type %d or accessor type %d", c, t))
}

// MakeSliceBuffer returns the slice type associated with c and t and with the given element count.
// If the buffer is an slice which type matches with the expected by the acr then it will
// be used as backing slice.
func MakeSliceBuffer(c gltf.ComponentType, t gltf.AccessorType, count int, buffer []byte) (any, error) {
if len(buffer) == 0 {
return MakeSlice(c, t, count)
if err := checkAccessorType(c, t); err != nil {
return nil, err
}
v := castSlice(c, t, buffer)
if v == nil {
buffer = buffer[:cap(buffer)] // Extend the slice to its capacity.
if len(buffer) == 0 {
return MakeSlice(c, t, count)
}
count1 := reflect.ValueOf(v).Len()
if count1 < count {
tmpSlice, _ := MakeSlice(c, t, count-count1)
return reflect.AppendSlice(reflect.ValueOf(v), reflect.ValueOf(tmpSlice)).Interface(), nil
}
if count1 > count {
return reflect.ValueOf(v).Slice(0, int(count)).Interface(), nil
}
return v, nil
return castSlice(c, t, count, buffer), nil
}

// MakeSlice returns the slice type associated with c and t and with the given element count.
// For example, if c is gltf.ComponentFloat and t is gltf.AccessorVec3
// then MakeSlice(c, t, 5) is equivalent to make([][3]float32, 5).
func MakeSlice(c gltf.ComponentType, t gltf.AccessorType, count int) (any, error) {
if err := checkAccessorType(c, t); err != nil {
return nil, err
}
var tp reflect.Type
switch c {
case gltf.ComponentUbyte:
Expand All @@ -162,7 +174,7 @@ func MakeSlice(c gltf.ComponentType, t gltf.AccessorType, count int) (any, error
case gltf.ComponentFloat:
tp = reflect.TypeOf((*float32)(nil))
default:
return nil, fmt.Errorf("gltf: unsupported component type %d", c)
panic(fmt.Errorf("gltf: unsupported component type %d", c))
}
tp = tp.Elem()
switch t {
Expand All @@ -181,7 +193,7 @@ func MakeSlice(c gltf.ComponentType, t gltf.AccessorType, count int) (any, error
case gltf.AccessorMat4:
tp = reflect.ArrayOf(4, reflect.ArrayOf(4, tp))
default:
return nil, fmt.Errorf("gltf: unsupported accessor type %d", t)
panic(fmt.Errorf("gltf: unsupported accessor type %d", t))
}
return reflect.MakeSlice(reflect.SliceOf(tp), count, count).Interface(), nil
}
Expand Down Expand Up @@ -284,3 +296,19 @@ func Type(data any) (c gltf.ComponentType, t gltf.AccessorType, count int) {
}
return
}

func checkAccessorType(c gltf.ComponentType, t gltf.AccessorType) error {
switch c {
case gltf.ComponentUbyte, gltf.ComponentByte, gltf.ComponentUshort,
gltf.ComponentShort, gltf.ComponentUint, gltf.ComponentFloat:
default:
return fmt.Errorf("gltf: unsupported component type %d", c)
}
switch t {
case gltf.AccessorScalar, gltf.AccessorVec2, gltf.AccessorVec3,
gltf.AccessorVec4, gltf.AccessorMat2, gltf.AccessorMat3, gltf.AccessorMat4:
default:
return fmt.Errorf("gltf: unsupported accessor type %d", t)
}
return nil
}
84 changes: 84 additions & 0 deletions modeler/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,94 @@ func BenchmarkReadAccessorSparse(b *testing.B) {
Values: gltf.SparseValues{BufferView: 2},
},
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, err := modeler.ReadAccessor(doc, acr, nil)
if err != nil {
b.Fatal(err)
}
}
}

func BenchmarkReadAccessorBuffer(b *testing.B) {
doc := &gltf.Document{
Buffers: []*gltf.Buffer{{ByteLength: 52, Data: []byte{
0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64,
0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64,
0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64,
0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64,
}}}, BufferViews: []*gltf.BufferView{{Buffer: 0, ByteLength: 48}},
}
acr := &gltf.Accessor{
BufferView: gltf.Index(0), ComponentType: gltf.ComponentFloat, Type: gltf.AccessorVec3, Count: 4,
}
buf := make([]byte, 48)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, err := modeler.ReadAccessor(doc, acr, buf)
if err != nil {
b.Fatal(err)
}
}
}

func BenchmarkReadAccessor(b *testing.B) {
doc := &gltf.Document{
Buffers: []*gltf.Buffer{{ByteLength: 52, Data: []byte{
0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64,
0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64,
0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64,
0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64,
}}}, BufferViews: []*gltf.BufferView{{Buffer: 0, ByteLength: 48}},
}
acr := &gltf.Accessor{
BufferView: gltf.Index(0), ComponentType: gltf.ComponentFloat, Type: gltf.AccessorVec3, Count: 4,
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, err := modeler.ReadAccessor(doc, acr, nil)
if err != nil {
b.Fatal(err)
}
}
}

func BenchmarkReadIndices(b *testing.B) {
doc := &gltf.Document{
Buffers: []*gltf.Buffer{{ByteLength: 8, Data: []byte{1, 0, 0, 0, 2, 0, 0, 0}}},
BufferViews: []*gltf.BufferView{{Buffer: 0, ByteLength: 8}},
}
acr := &gltf.Accessor{
BufferView: gltf.Index(0), ComponentType: gltf.ComponentUint, Type: gltf.AccessorScalar, Count: 2,
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, err := modeler.ReadIndices(doc, acr, nil)
if err != nil {
b.Fatal(err)
}
}
}

func BenchmarkReadIndicesBuffer(b *testing.B) {
doc := &gltf.Document{
Buffers: []*gltf.Buffer{{ByteLength: 8, Data: []byte{1, 0, 0, 0, 2, 0, 0, 0}}},
BufferViews: []*gltf.BufferView{{Buffer: 0, ByteLength: 8}},
}
acr := &gltf.Accessor{
BufferView: gltf.Index(0), ComponentType: gltf.ComponentUint, Type: gltf.AccessorScalar, Count: 2,
}
buf := make([]uint32, 6)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, err := modeler.ReadIndices(doc, acr, buf)
if err != nil {
b.Fatal(err)
}
}
}
8 changes: 4 additions & 4 deletions modeler/read_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,10 +216,10 @@ func TestReadAccessorAllocs(t *testing.T) {
})
t.Run("2", func(t *testing.T) {
buf := make([]byte, 24)
testFunc(t, buf, 6)
testFunc(t, buf, 6)
testFunc(t, buf, 6)
testFunc(t, buf, 6)
testFunc(t, buf, 1)
testFunc(t, buf, 1)
testFunc(t, buf, 1)
testFunc(t, buf, 1)
})
t.Run("4", func(t *testing.T) {
buf := make([]byte, 48)
Expand Down

0 comments on commit 2344d28

Please sign in to comment.