Repository: qmuntal/gltf
Pull request #88 (2024-08-28 13:12:15)

Improve modeler.ReadAccessor buffer handling
`modeler.ReadAccessor` now takes a `[]byte` as the buffer parameter instead of `any`. It will internally cast that slice to the appropriate type and use it as backing memory when reading the accessor data.

This new approach has the advantage of allowing to reuse the same buffer for different accessor types. Previously, the buffer would only be used if the buffer type would match the accessor type.

Additionally, all the other `modeler.Read*` functions have been updated to pass a pooled buffer into `modeler.ReadAccessor` instead of the buffer passed by the caller.


Patch:
diff --git a/binary/slice.go b/binary/slice.go
index 5c0bcf1..ecb42d9 100644
--- a/binary/slice.go
+++ b/binary/slice.go
@@ -4,35 +4,149 @@ import (
 	"fmt"
 	"image/color"
 	"reflect"
+	"unsafe"
 
 	"github.com/qmuntal/gltf"
 )
 
+func castSlice(c gltf.ComponentType, t gltf.AccessorType, v []byte) any {
+	var ptr unsafe.Pointer
+	if len(v) != 0 {
+		ptr = unsafe.Pointer(&v[0])
+	}
+	switch c {
+	case gltf.ComponentUbyte:
+		switch t {
+		case gltf.AccessorScalar:
+			return v
+		case gltf.AccessorVec2:
+			return unsafe.Slice((*[2]uint8)(ptr), len(v)/2)
+		case gltf.AccessorVec3:
+			return unsafe.Slice((*[3]uint8)(ptr), len(v)/3)
+		case gltf.AccessorVec4:
+			return unsafe.Slice((*[4]uint8)(ptr), len(v)/4)
+		case gltf.AccessorMat2:
+			return unsafe.Slice((*[2][2]uint8)(ptr), len(v)/4)
+		case gltf.AccessorMat3:
+			return unsafe.Slice((*[3][3]uint8)(ptr), len(v)/9)
+		case gltf.AccessorMat4:
+			return unsafe.Slice((*[4][4]uint8)(ptr), len(v)/16)
+		}
+	case gltf.ComponentByte:
+		switch t {
+		case gltf.AccessorScalar:
+			return unsafe.Slice((*int8)(ptr), len(v))
+		case gltf.AccessorVec2:
+			return unsafe.Slice((*[2]int8)(ptr), len(v)/2)
+		case gltf.AccessorVec3:
+			return unsafe.Slice((*[3]int8)(ptr), len(v)/3)
+		case gltf.AccessorVec4:
+			return unsafe.Slice((*[4]int8)(ptr), len(v)/4)
+		case gltf.AccessorMat2:
+			return unsafe.Slice((*[2][2]int8)(ptr), len(v)/4)
+		case gltf.AccessorMat3:
+			return unsafe.Slice((*[3][3]int8)(ptr), len(v)/9)
+		case gltf.AccessorMat4:
+			return unsafe.Slice((*[4][4]int8)(ptr), len(v)/16)
+		}
+	case gltf.ComponentUshort:
+		switch t {
+		case gltf.AccessorScalar:
+			return unsafe.Slice((*uint16)(ptr), len(v)/2)
+		case gltf.AccessorVec2:
+			return unsafe.Slice((*[2]uint16)(ptr), len(v)/4)
+		case gltf.AccessorVec3:
+			return unsafe.Slice((*[3]uint16)(ptr), len(v)/6)
+		case gltf.AccessorVec4:
+			return unsafe.Slice((*[4]uint16)(ptr), len(v)/8)
+		case gltf.AccessorMat2:
+			return unsafe.Slice((*[2][2]uint16)(ptr), len(v)/8)
+		case gltf.AccessorMat3:
+			return unsafe.Slice((*[3][3]uint16)(ptr), len(v)/18)
+		case gltf.AccessorMat4:
+			return unsafe.Slice((*[4][4]uint16)(ptr), len(v)/32)
+		}
+	case gltf.ComponentShort:
+		switch t {
+		case gltf.AccessorScalar:
+			return unsafe.Slice((*int16)(ptr), len(v)/2)
+		case gltf.AccessorVec2:
+			return unsafe.Slice((*[2]int16)(ptr), len(v)/4)
+		case gltf.AccessorVec3:
+			return unsafe.Slice((*[3]int16)(ptr), len(v)/6)
+		case gltf.AccessorVec4:
+			return unsafe.Slice((*[4]int16)(ptr), len(v)/8)
+		case gltf.AccessorMat2:
+			return unsafe.Slice((*[2][2]int16)(ptr), len(v)/8)
+		case gltf.AccessorMat3:
+			return unsafe.Slice((*[3][3]int16)(ptr), len(v)/18)
+		case gltf.AccessorMat4:
+			return unsafe.Slice((*[4][4]int16)(ptr), len(v)/32)
+		}
+	case gltf.ComponentUint:
+		switch t {
+		case gltf.AccessorScalar:
+			return unsafe.Slice((*uint32)(ptr), len(v)/4)
+		case gltf.AccessorVec2:
+			return unsafe.Slice((*[2]uint32)(ptr), len(v)/8)
+		case gltf.AccessorVec3:
+			return unsafe.Slice((*[3]uint32)(ptr), len(v)/12)
+		case gltf.AccessorVec4:
+			return unsafe.Slice((*[4]uint32)(ptr), len(v)/16)
+		case gltf.AccessorMat2:
+			return unsafe.Slice((*[2][2]uint32)(ptr), len(v)/16)
+		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)
+		}
+	case gltf.ComponentFloat:
+		switch t {
+		case gltf.AccessorScalar:
+			return unsafe.Slice((*float32)(ptr), len(v)/4)
+		case gltf.AccessorVec2:
+			return unsafe.Slice((*[2]float32)(ptr), len(v)/8)
+		case gltf.AccessorVec3:
+			return unsafe.Slice((*[3]float32)(ptr), len(v)/12)
+		case gltf.AccessorVec4:
+			return unsafe.Slice((*[4]float32)(ptr), len(v)/16)
+		case gltf.AccessorMat2:
+			return unsafe.Slice((*[2][2]float32)(ptr), len(v)/16)
+		case gltf.AccessorMat3:
+			return unsafe.Slice((*[3][3]float32)(ptr), len(v)/36)
+		case gltf.AccessorMat4:
+			return unsafe.Slice((*[4][4]float32)(ptr), len(v)/64)
+		}
+	}
+	return nil
+}
+
 // 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 any) any {
-	if buffer == nil {
+func MakeSliceBuffer(c gltf.ComponentType, t gltf.AccessorType, count int, buffer []byte) (any, error) {
+	if len(buffer) == 0 {
 		return MakeSlice(c, t, count)
 	}
-	c1, t1, count1 := Type(buffer)
-	if count1 == 0 || c1 != c || t1 != t {
+	v := castSlice(c, t, buffer)
+	if v == nil {
 		return MakeSlice(c, t, count)
 	}
+	count1 := reflect.ValueOf(v).Len()
 	if count1 < count {
-		tmpSlice := MakeSlice(c, t, count-count1)
-		return reflect.AppendSlice(reflect.ValueOf(buffer), reflect.ValueOf(tmpSlice)).Interface()
+		tmpSlice, _ := MakeSlice(c, t, count-count1)
+		return reflect.AppendSlice(reflect.ValueOf(v), reflect.ValueOf(tmpSlice)).Interface(), nil
 	}
 	if count1 > count {
-		return reflect.ValueOf(buffer).Slice(0, int(count)).Interface()
+		return reflect.ValueOf(v).Slice(0, int(count)).Interface(), nil
 	}
-	return buffer
+	return v, 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 {
+func MakeSlice(c gltf.ComponentType, t gltf.AccessorType, count int) (any, error) {
 	var tp reflect.Type
 	switch c {
 	case gltf.ComponentUbyte:
@@ -47,9 +161,13 @@ func MakeSlice(c gltf.ComponentType, t gltf.AccessorType, count int) any {
 		tp = reflect.TypeOf((*uint32)(nil))
 	case gltf.ComponentFloat:
 		tp = reflect.TypeOf((*float32)(nil))
+	default:
+		return nil, fmt.Errorf("gltf: unsupported component type %d", c)
 	}
 	tp = tp.Elem()
 	switch t {
+	case gltf.AccessorScalar:
+		// Nothing to do.
 	case gltf.AccessorVec2:
 		tp = reflect.ArrayOf(2, tp)
 	case gltf.AccessorVec3:
@@ -62,8 +180,10 @@ func MakeSlice(c gltf.ComponentType, t gltf.AccessorType, count int) any {
 		tp = reflect.ArrayOf(3, reflect.ArrayOf(3, tp))
 	case gltf.AccessorMat4:
 		tp = reflect.ArrayOf(4, reflect.ArrayOf(4, tp))
+	default:
+		return nil, fmt.Errorf("gltf: unsupported accessor type %d", t)
 	}
-	return reflect.MakeSlice(reflect.SliceOf(tp), count, count).Interface()
+	return reflect.MakeSlice(reflect.SliceOf(tp), count, count).Interface(), nil
 }
 
 // Type returns the associated glTF type data.
diff --git a/modeler/read.go b/modeler/read.go
index be040c4..254aaab 100644
--- a/modeler/read.go
+++ b/modeler/read.go
@@ -5,35 +5,37 @@ import (
 	"fmt"
 	"io"
 	"reflect"
+	"sync"
 
 	"github.com/qmuntal/gltf"
 	"github.com/qmuntal/gltf/binary"
 )
 
-// ReadAccessor returns the data references by acr
-// as an slice whose element types are the ones associated with
-// acr.ComponentType and acr.Type.
+// ReadAccessor returns the data references by acr as an slice
+// whose element types are the ones associated with acr.ComponentType and acr.Type.
 //
-// If data is an slice whose elements type matches the accessor type
-// then data will be used as backing slice, else a new slice will be allocated.
+// If buffer is not nil, it will be used as backing slice.
 //
 // ReadAccessor supports all types of accessors: non-interleaved, interleaved, sparse,
 // without buffer views, ..., and any combinations of them.
 //
 // ReadAccessor is safe to use even with malformed documents.
 // If that happens it will return an error instead of panic.
-func ReadAccessor(doc *gltf.Document, acr *gltf.Accessor, buffer any) (any, error) {
+func ReadAccessor(doc *gltf.Document, acr *gltf.Accessor, buffer []byte) (any, error) {
 	if acr.BufferView == nil && acr.Sparse == nil {
 		return nil, nil
 	}
-	buffer = binary.MakeSliceBuffer(acr.ComponentType, acr.Type, acr.Count, buffer)
+	data, err := binary.MakeSliceBuffer(acr.ComponentType, acr.Type, acr.Count, buffer)
+	if err != nil {
+		return nil, err
+	}
 	if acr.BufferView != nil {
 		buf, err := readBufferView(doc, *acr.BufferView)
 		if err != nil {
 			return nil, err
 		}
 		byteStride := doc.BufferViews[*acr.BufferView].ByteStride
-		err = binary.Read(buf[acr.ByteOffset:], byteStride, buffer)
+		err = binary.Read(buf[acr.ByteOffset:], byteStride, data)
 		if err != nil {
 			return nil, err
 		}
@@ -46,7 +48,10 @@ func ReadAccessor(doc *gltf.Document, acr *gltf.Accessor, buffer any) (any, erro
 		}
 
 		byteStride := doc.BufferViews[acr.Sparse.Indices.BufferView].ByteStride
-		indices := binary.MakeSlice(acr.Sparse.Indices.ComponentType, gltf.AccessorScalar, acr.Sparse.Count)
+		indices, err := binary.MakeSlice(acr.Sparse.Indices.ComponentType, gltf.AccessorScalar, acr.Sparse.Count)
+		if err != nil {
+			return nil, err
+		}
 		err = binary.Read(indicesBuffer[acr.Sparse.Indices.ByteOffset:], byteStride, indices)
 		if err != nil {
 			return nil, err
@@ -57,13 +62,16 @@ func ReadAccessor(doc *gltf.Document, acr *gltf.Accessor, buffer any) (any, erro
 			return nil, err
 		}
 		byteStride = doc.BufferViews[acr.Sparse.Values.ByteOffset].ByteStride
-		values := binary.MakeSlice(acr.ComponentType, acr.Type, acr.Sparse.Count)
+		values, err := binary.MakeSlice(acr.ComponentType, acr.Type, acr.Sparse.Count)
+		if err != nil {
+			return nil, err
+		}
 		err = binary.Read(valuesBuffer[acr.Sparse.Values.ByteOffset:], byteStride, values)
 		if err != nil {
 			return nil, err
 		}
 
-		s := reflect.ValueOf(buffer)
+		s := reflect.ValueOf(data)
 		ind := reflect.ValueOf(indices)
 		vals := reflect.ValueOf(values)
 		for i := 0; i < int(acr.Sparse.Count); i++ {
@@ -87,7 +95,7 @@ func ReadAccessor(doc *gltf.Document, acr *gltf.Accessor, buffer any) (any, erro
 			s.Index(id).Set(vals.Index(i))
 		}
 	}
-	return buffer, nil
+	return data, nil
 }
 
 func readBufferView(doc *gltf.Document, bufferViewIndex int) ([]byte, error) {
@@ -98,6 +106,7 @@ func readBufferView(doc *gltf.Document, bufferViewIndex int) ([]byte, error) {
 }
 
 // ReadBufferView returns the slice of bytes associated with the BufferView.
+// The slice is a view of the buffer data, so it is not safe to modify it.
 //
 // It is safe to use even with malformed documents.
 // If that happens it will return an error instead of panic.
@@ -114,6 +123,22 @@ func ReadBufferView(doc *gltf.Document, bv *gltf.BufferView) ([]byte, error) {
 	return buf[bv.ByteOffset:high], nil
 }
 
+var bufPool = sync.Pool{
+	New: func() any {
+		buf := make([]byte, 1024)
+		return &buf
+	},
+}
+
+func makeBufferOf[T any](count int, buffer []T) []T {
+	if len(buffer) < count {
+		buffer = append(buffer, make([]T, count-len(buffer))...)
+	} else {
+		buffer = buffer[:count]
+	}
+	return buffer
+}
+
 // ReadIndices returns the data referenced by acr.
 // If acr.ComponentType is other than Uint the data
 // will be converted appropriately.
@@ -128,26 +153,24 @@ func ReadIndices(doc *gltf.Document, acr *gltf.Accessor, buffer []uint32) ([]uin
 	if acr.Type != gltf.AccessorScalar {
 		return nil, errAccessorType(acr.Type)
 	}
-	data, err := ReadAccessor(doc, acr, buffer)
+	bufPtr := bufPool.Get().(*[]byte)
+	defer bufPool.Put(bufPtr)
+	data, err := ReadAccessor(doc, acr, *bufPtr)
 	if err != nil {
 		return nil, err
 	}
-	if len(buffer) < acr.Count {
-		buffer = append(buffer, make([]uint32, acr.Count-len(buffer))...)
-	} else {
-		buffer = buffer[:acr.Count]
-	}
-	switch acr.ComponentType {
-	case gltf.ComponentUbyte:
-		for i, e := range data.([]uint8) {
+	buffer = makeBufferOf(acr.Count, buffer)
+	switch data := data.(type) {
+	case []uint8:
+		for i, e := range data {
 			buffer[i] = uint32(e)
 		}
-	case gltf.ComponentUshort:
-		for i, e := range data.([]uint16) {
+	case []uint16:
+		for i, e := range data {
 			buffer[i] = uint32(e)
 		}
-	case gltf.ComponentUint:
-		buffer = data.([]uint32)
+	case []uint32:
+		copy(buffer, data)
 	}
 	return buffer, nil
 }
@@ -162,11 +185,15 @@ func ReadNormal(doc *gltf.Document, acr *gltf.Accessor, buffer [][3]float32) ([]
 	if acr.Type != gltf.AccessorVec3 {
 		return nil, errAccessorType(acr.Type)
 	}
-	data, err := ReadAccessor(doc, acr, buffer)
+	bufPtr := bufPool.Get().(*[]byte)
+	defer bufPool.Put(bufPtr)
+	data, err := ReadAccessor(doc, acr, *bufPtr)
 	if err != nil {
 		return nil, err
 	}
-	return data.([][3]float32), nil
+	buffer = makeBufferOf(acr.Count, buffer)
+	copy(buffer, data.([][3]float32))
+	return buffer, nil
 }
 
 // ReadTangent returns the data referenced by acr.
@@ -179,11 +206,15 @@ func ReadTangent(doc *gltf.Document, acr *gltf.Accessor, buffer [][4]float32) ([
 	if acr.Type != gltf.AccessorVec4 {
 		return nil, errAccessorType(acr.Type)
 	}
-	data, err := ReadAccessor(doc, acr, buffer)
+	bufPtr := bufPool.Get().(*[]byte)
+	defer bufPool.Put(bufPtr)
+	data, err := ReadAccessor(doc, acr, *bufPtr)
 	if err != nil {
 		return nil, err
 	}
-	return data.([][4]float32), nil
+	buffer = makeBufferOf(acr.Count, buffer)
+	copy(buffer, data.([][4]float32))
+	return buffer, nil
 }
 
 // ReadTextureCoord returns the data referenced by acr.
@@ -200,30 +231,28 @@ func ReadTextureCoord(doc *gltf.Document, acr *gltf.Accessor, buffer [][2]float3
 	if acr.Type != gltf.AccessorVec2 {
 		return nil, errAccessorType(acr.Type)
 	}
-	data, err := ReadAccessor(doc, acr, buffer)
+	bufPtr := bufPool.Get().(*[]byte)
+	defer bufPool.Put(bufPtr)
+	data, err := ReadAccessor(doc, acr, *bufPtr)
 	if err != nil {
 		return nil, err
 	}
-	if len(buffer) < acr.Count {
-		buffer = append(buffer, make([][2]float32, acr.Count-len(buffer))...)
-	} else {
-		buffer = buffer[:acr.Count]
-	}
-	switch acr.ComponentType {
-	case gltf.ComponentUbyte:
-		for i, e := range data.([][2]uint8) {
+	buffer = makeBufferOf(acr.Count, buffer)
+	switch data := data.(type) {
+	case [][2]uint8:
+		for i, e := range data {
 			buffer[i] = [2]float32{
 				gltf.DenormalizeUbyte(e[0]), gltf.DenormalizeUbyte(e[1]),
 			}
 		}
-	case gltf.ComponentUshort:
-		for i, e := range data.([][2]uint16) {
+	case [][2]uint16:
+		for i, e := range data {
 			buffer[i] = [2]float32{
 				gltf.DenormalizeUshort(e[0]), gltf.DenormalizeUshort(e[1]),
 			}
 		}
-	case gltf.ComponentFloat:
-		buffer = data.([][2]float32)
+	case [][2]float32:
+		copy(buffer, data)
 	}
 	return buffer, nil
 }
@@ -242,32 +271,30 @@ func ReadWeights(doc *gltf.Document, acr *gltf.Accessor, buffer [][4]float32) ([
 	if acr.Type != gltf.AccessorVec4 {
 		return nil, errAccessorType(acr.Type)
 	}
-	data, err := ReadAccessor(doc, acr, buffer)
+	bufPtr := bufPool.Get().(*[]byte)
+	defer bufPool.Put(bufPtr)
+	data, err := ReadAccessor(doc, acr, *bufPtr)
 	if err != nil {
 		return nil, err
 	}
-	if len(buffer) < acr.Count {
-		buffer = append(buffer, make([][4]float32, acr.Count-len(buffer))...)
-	} else {
-		buffer = buffer[:acr.Count]
-	}
-	switch acr.ComponentType {
-	case gltf.ComponentUbyte:
-		for i, e := range data.([][4]uint8) {
+	buffer = makeBufferOf(acr.Count, buffer)
+	switch data := data.(type) {
+	case [][4]uint8:
+		for i, e := range data {
 			buffer[i] = [4]float32{
 				gltf.DenormalizeUbyte(e[0]), gltf.DenormalizeUbyte(e[1]),
 				gltf.DenormalizeUbyte(e[2]), gltf.DenormalizeUbyte(e[3]),
 			}
 		}
-	case gltf.ComponentUshort:
-		for i, e := range data.([][4]uint16) {
+	case [][4]uint16:
+		for i, e := range data {
 			buffer[i] = [4]float32{
 				gltf.DenormalizeUshort(e[0]), gltf.DenormalizeUshort(e[1]),
 				gltf.DenormalizeUshort(e[2]), gltf.DenormalizeUshort(e[3]),
 			}
 		}
-	case gltf.ComponentFloat:
-		buffer = data.([][4]float32)
+	case [][4]float32:
+		copy(buffer, data)
 	}
 	return buffer, nil
 }
@@ -286,25 +313,23 @@ func ReadJoints(doc *gltf.Document, acr *gltf.Accessor, buffer [][4]uint16) ([][
 	if acr.Type != gltf.AccessorVec4 {
 		return nil, errAccessorType(acr.Type)
 	}
-	data, err := ReadAccessor(doc, acr, buffer)
+	bufPtr := bufPool.Get().(*[]byte)
+	defer bufPool.Put(bufPtr)
+	data, err := ReadAccessor(doc, acr, *bufPtr)
 	if err != nil {
 		return nil, err
 	}
-	if len(buffer) < acr.Count {
-		buffer = append(buffer, make([][4]uint16, acr.Count-len(buffer))...)
-	} else {
-		buffer = buffer[:acr.Count]
-	}
-	switch acr.ComponentType {
-	case gltf.ComponentUbyte:
-		for i, e := range data.([][4]uint8) {
+	buffer = makeBufferOf(acr.Count, buffer)
+	switch data := data.(type) {
+	case [][4]uint8:
+		for i, e := range data {
 			buffer[i] = [4]uint16{
 				uint16(e[0]), uint16(e[1]),
 				uint16(e[2]), uint16(e[3]),
 			}
 		}
-	case gltf.ComponentUshort:
-		buffer = data.([][4]uint16)
+	case [][4]uint16:
+		copy(buffer, data)
 	}
 	return buffer, nil
 }
@@ -319,11 +344,15 @@ func ReadPosition(doc *gltf.Document, acr *gltf.Accessor, buffer [][3]float32) (
 	if acr.Type != gltf.AccessorVec3 {
 		return nil, errAccessorType(acr.Type)
 	}
-	data, err := ReadAccessor(doc, acr, buffer)
+	bufPtr := bufPool.Get().(*[]byte)
+	defer bufPool.Put(bufPtr)
+	data, err := ReadAccessor(doc, acr, *bufPtr)
 	if err != nil {
 		return nil, err
 	}
-	return data.([][3]float32), nil
+	buffer = makeBufferOf(acr.Count, buffer)
+	copy(buffer, data.([][3]float32))
+	return buffer, nil
 }
 
 // ReadColor returns the data referenced by acr.
@@ -342,44 +371,36 @@ func ReadColor(doc *gltf.Document, acr *gltf.Accessor, buffer [][4]uint8) ([][4]
 	default:
 		return nil, errAccessorType(acr.Type)
 	}
-	data, err := ReadAccessor(doc, acr, buffer)
+	bufPtr := bufPool.Get().(*[]byte)
+	defer bufPool.Put(bufPtr)
+	data, err := ReadAccessor(doc, acr, *bufPtr)
 	if err != nil {
 		return nil, err
 	}
-	if len(buffer) < acr.Count {
-		buffer = append(buffer, make([][4]uint8, acr.Count-len(buffer))...)
-	} else {
-		buffer = buffer[:acr.Count]
-	}
-	switch acr.ComponentType {
-	case gltf.ComponentUbyte:
-		if acr.Type == gltf.AccessorVec3 {
-			for i, e := range data.([][3]uint8) {
-				buffer[i] = [4]uint8{e[0], e[1], e[2], 255}
-			}
-		} else {
-			buffer = data.([][4]uint8)
+	buffer = makeBufferOf(acr.Count, buffer)
+	switch data := data.(type) {
+	case [][3]uint8:
+		for i, e := range data {
+			buffer[i] = [4]uint8{e[0], e[1], e[2], 255}
 		}
-	case gltf.ComponentUshort:
-		if acr.Type == gltf.AccessorVec3 {
-			for i, e := range data.([][3]uint16) {
-				buffer[i] = [4]uint8{uint8(e[0]), uint8(e[1]), uint8(e[2]), 255}
-			}
-		} else {
-			for i, e := range data.([][4]uint16) {
-				buffer[i] = [4]uint8{uint8(e[0]), uint8(e[1]), uint8(e[2]), uint8(e[3])}
-			}
+	case [][4]uint8:
+		copy(buffer, data)
+	case [][3]uint16:
+		for i, e := range data {
+			buffer[i] = [4]uint8{uint8(e[0]), uint8(e[1]), uint8(e[2]), 255}
 		}
-	case gltf.ComponentFloat:
-		if acr.Type == gltf.AccessorVec3 {
-			for i, e := range data.([][3]float32) {
-				tmp := gltf.NormalizeRGB(e)
-				buffer[i] = [4]uint8{tmp[0], tmp[1], tmp[2], 255}
-			}
-		} else {
-			for i, e := range data.([][4]float32) {
-				buffer[i] = gltf.NormalizeRGBA(e)
-			}
+	case [][4]uint16:
+		for i, e := range data {
+			buffer[i] = [4]uint8{uint8(e[0]), uint8(e[1]), uint8(e[2]), uint8(e[3])}
+		}
+	case [][3]float32:
+		for i, e := range data {
+			tmp := gltf.NormalizeRGB(e)
+			buffer[i] = [4]uint8{tmp[0], tmp[1], tmp[2], 255}
+		}
+	case [][4]float32:
+		for i, e := range data {
+			buffer[i] = gltf.NormalizeRGBA(e)
 		}
 	}
 	return buffer, nil
@@ -401,53 +422,46 @@ func ReadColor64(doc *gltf.Document, acr *gltf.Accessor, buffer [][4]uint16) ([]
 	default:
 		return nil, errAccessorType(acr.Type)
 	}
-	data, err := ReadAccessor(doc, acr, buffer)
+	bufPtr := bufPool.Get().(*[]byte)
+	defer bufPool.Put(bufPtr)
+	data, err := ReadAccessor(doc, acr, *bufPtr)
 	if err != nil {
 		return nil, err
 	}
-	if len(buffer) < acr.Count {
-		buffer = append(buffer, make([][4]uint16, acr.Count-len(buffer))...)
-	} else {
-		buffer = buffer[:acr.Count]
-	}
-	switch acr.ComponentType {
-	case gltf.ComponentUbyte:
-		if acr.Type == gltf.AccessorVec3 {
-			for i, e := range data.([][3]uint8) {
-				buffer[i] = [4]uint16{
-					uint16(e[0]) | uint16(e[0])<<8,
-					uint16(e[1]) | uint16(e[1])<<8,
-					uint16(e[2]) | uint16(e[2])<<8,
-					65535}
-			}
-		} else {
-			for i, e := range data.([][4]uint8) {
-				buffer[i] = [4]uint16{
-					uint16(e[0]) | uint16(e[0])<<8,
-					uint16(e[1]) | uint16(e[1])<<8,
-					uint16(e[2]) | uint16(e[2])<<8,
-					uint16(e[3]) | uint16(e[3])<<8,
-				}
+	buffer = makeBufferOf(acr.Count, buffer)
+	switch data := data.(type) {
+	case [][3]uint8:
+		for i, e := range data {
+			buffer[i] = [4]uint16{
+				uint16(e[0]) | uint16(e[0])<<8,
+				uint16(e[1]) | uint16(e[1])<<8,
+				uint16(e[2]) | uint16(e[2])<<8,
+				65535,
 			}
 		}
-	case gltf.ComponentUshort:
-		if acr.Type == gltf.AccessorVec3 {
-			for i, e := range data.([][3]uint16) {
-				buffer[i] = [4]uint16{e[0], e[1], e[2], 65535}
+	case [][4]uint8:
+		for i, e := range data {
+			buffer[i] = [4]uint16{
+				uint16(e[0]) | uint16(e[0])<<8,
+				uint16(e[1]) | uint16(e[1])<<8,
+				uint16(e[2]) | uint16(e[2])<<8,
+				uint16(e[3]) | uint16(e[3])<<8,
 			}
-		} else {
-			buffer = data.([][4]uint16)
 		}
-	case gltf.ComponentFloat:
-		if acr.Type == gltf.AccessorVec3 {
-			for i, e := range data.([][3]float32) {
-				tmp := gltf.NormalizeRGB64(e)
-				buffer[i] = [4]uint16{tmp[0], tmp[1], tmp[2], 65535}
-			}
-		} else {
-			for i, e := range data.([][4]float32) {
-				buffer[i] = gltf.NormalizeRGBA64(e)
-			}
+	case [][3]uint16:
+		for i, e := range data {
+			buffer[i] = [4]uint16{e[0], e[1], e[2], 65535}
+		}
+	case [][4]uint16:
+		copy(buffer, data)
+	case [][3]float32:
+		for i, e := range data {
+			tmp := gltf.NormalizeRGB64(e)
+			buffer[i] = [4]uint16{tmp[0], tmp[1], tmp[2], 65535}
+		}
+	case [][4]float32:
+		for i, e := range data {
+			buffer[i] = gltf.NormalizeRGBA64(e)
 		}
 	}
 	return buffer, nil


Test patch:
diff --git a/binary/slice_test.go b/binary/slice_test.go
index fb7edc2..6232076 100644
--- a/binary/slice_test.go
+++ b/binary/slice_test.go
@@ -71,7 +71,7 @@ func TestMakeSlice(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			if got := binary.MakeSlice(tt.args.c, tt.args.t, tt.args.count); !reflect.DeepEqual(got, tt.want) {
+			if got, _ := binary.MakeSlice(tt.args.c, tt.args.t, tt.args.count); !reflect.DeepEqual(got, tt.want) {
 				t.Errorf("MakeSlice() = %v, want %v", got, tt.want)
 			}
 		})
@@ -83,7 +83,7 @@ func TestMakeSliceBuffer(t *testing.T) {
 		c      gltf.ComponentType
 		t      gltf.AccessorType
 		count  int
-		buffer any
+		buffer []byte
 	}
 	tests := []struct {
 		name string
@@ -91,15 +91,15 @@ func TestMakeSliceBuffer(t *testing.T) {
 		want any
 	}{
 		{"nil buffer", args{gltf.ComponentUbyte, gltf.AccessorVec2, 2, nil}, make([][2]uint8, 2)},
-		{"empty buffer", args{gltf.ComponentUbyte, gltf.AccessorVec2, 2, make([][2]uint8, 0)}, make([][2]uint8, 2)},
-		{"different buffer", args{gltf.ComponentUbyte, gltf.AccessorVec2, 2, make([][3]int8, 3)}, make([][2]uint8, 2)},
-		{"small buffer", args{gltf.ComponentUbyte, gltf.AccessorVec2, 2, make([][2]uint8, 1)}, make([][2]uint8, 2)},
-		{"large buffer", args{gltf.ComponentUbyte, gltf.AccessorVec2, 2, make([][2]uint8, 3)}, make([][2]uint8, 2)},
-		{"same buffer", args{gltf.ComponentUbyte, gltf.AccessorVec2, 2, make([][2]uint8, 2)}, make([][2]uint8, 2)},
+		{"empty buffer", args{gltf.ComponentUbyte, gltf.AccessorVec2, 2, make([]uint8, 0)}, make([][2]uint8, 2)},
+		{"different buffer", args{gltf.ComponentUbyte, gltf.AccessorVec2, 2, make([]uint8, 9)}, make([][2]uint8, 2)},
+		{"small buffer", args{gltf.ComponentUbyte, gltf.AccessorVec2, 2, make([]uint8, 2)}, make([][2]uint8, 2)},
+		{"large buffer", args{gltf.ComponentUbyte, gltf.AccessorVec2, 2, make([]uint8, 6)}, make([][2]uint8, 2)},
+		{"same buffer", args{gltf.ComponentUbyte, gltf.AccessorVec2, 2, make([]uint8, 4)}, make([][2]uint8, 2)},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			if got := binary.MakeSliceBuffer(tt.args.c, tt.args.t, tt.args.count, tt.args.buffer); !reflect.DeepEqual(got, tt.want) {
+			if got, _ := binary.MakeSliceBuffer(tt.args.c, tt.args.t, tt.args.count, tt.args.buffer); !reflect.DeepEqual(got, tt.want) {
 				t.Errorf("MakeSliceBuffer() = %v, want %v", got, tt.want)
 			}
 		})
diff --git a/modeler/read_test.go b/modeler/read_test.go
index 42b484b..585252f 100644
--- a/modeler/read_test.go
+++ b/modeler/read_test.go
@@ -202,7 +202,7 @@ func TestReadAccessorAllocs(t *testing.T) {
 		BufferView: gltf.Index(0), ComponentType: gltf.ComponentFloat, Type: gltf.AccessorVec3, Count: 4,
 	}
 
-	testFunc := func(t *testing.T, buf [][3]float32, want float32) {
+	testFunc := func(t *testing.T, buf []byte, want float32) {
 		allocs := testing.AllocsPerRun(10, func() {
 			modeler.ReadAccessor(doc, acr, buf)
 		})
@@ -215,14 +215,14 @@ func TestReadAccessorAllocs(t *testing.T) {
 		testFunc(t, nil, 2)
 	})
 	t.Run("2", func(t *testing.T) {
-		buf := make([][3]float32, 2)
+		buf := make([]byte, 24)
 		testFunc(t, buf, 6)
 		testFunc(t, buf, 6)
 		testFunc(t, buf, 6)
 		testFunc(t, buf, 6)
 	})
 	t.Run("4", func(t *testing.T) {
-		buf := make([][3]float32, 4)
+		buf := make([]byte, 48)
 		testFunc(t, buf, 1)
 		testFunc(t, buf, 1)
 		testFunc(t, buf, 1)