Preparing for Swift 4 - Unsafemutablepointer Migration to Unsafemutablebufferpointer

In Swift 3.1, UnsafeMutablePointer.initialize(from:) is deprecated

It is correct, but you can allocate and initialize memory slightly simpler with

let pointer = UnsafeMutablePointer<UInt8>.allocate(capacity: 64)
pointer.initialize(to: 0, count: 64)

Creating a buffer pointer view can still be useful because that
is a collection, has a count property and can be enumerated:

let buffer = UnsafeMutableBufferPointer(start: pointer, count: 64)

for byte in buffer {
// ...
}

but that is independent of how the memory is initialized.

How to use UnsafeMutableBufferPointer

UnsafeMutableBufferPointer doesn't own its memory, so you still have to use UnsafeMutablePointer to allocate the underlying memory. But then you can use the buffer pointer as a collection, and enumerate it using for-in loops.

let count = 50
let ptr = UnsafeMutablePointer<Int>.alloc(count)
let buffer = UnsafeMutableBufferPointer(start: ptr, count: count)
for (i, _) in buffer.enumerate() {
buffer[i] = Int(arc4random())
}

// Do stuff...

ptr.dealloc(count) // Don't forget to dealloc!

Swift pointers don't provide realloc functionality. You could use the C functions, or roll your own if you want do that.

Swift: Cannot convert value of type 'UnsafeMutablePointer' to expected argument type 'UnsafeMutablePointer'

copyBytes expects a UnsafeMutableBufferPointer as argument:

extension Data {
func castToCPointer<T>() -> T {
let mem = UnsafeMutablePointer<T>.allocate(capacity: 1)
_ = self.copyBytes(to: UnsafeMutableBufferPointer(start: mem, count: 1))
return mem.move()
}
}

(allocate() takes the number of "items" as argument, not the number of
bytes.)

But note that your method leaks memory, the allocated memory
is deinitialized (with move()) but also has to be
deallocated:

extension Data {
func castToCPointer<T>() -> T {
let mem = UnsafeMutablePointer<T>.allocate(capacity: 1)
_ = self.copyBytes(to: UnsafeMutableBufferPointer(start: mem, count: 1))
let val = mem.move()
mem.deallocate(capacity: 1)
return val
}
}

A simpler solution would be (from
round trip Swift number types to/from Data):

extension Data {
func castToCPointer<T>() -> T {
return self.withUnsafeBytes { $0.pointee }
}
}

How to pass C array to Swift

There's no need to go through Obj-C. Assuming that output_f appears in an include file that's included via your bridging header, Swift will see its type as UnsafeMutablePointer<CFloat> (CFloat is just a typealias for Float, named to clarify that it corresponds to the C type).

Assuming you also make the number of floats in the array available, lets say included somewhere in your bridged header files is:

extern float* output_f;
extern int output_f_count;

Then on the Swift-side, you can use them like this:

let outputFloats = UnsafeMutableBufferPointer<CFloat>(
start: output_f,
count: Int(output_f_count))

The cast of output_f_count to Int is necessary because Swift interprets C's int as CInt (aka Int32).

You can use UnsafeMutablePointer much like array, but there's no copying. It just aliases the C data in Swift.

If you want to make sure you don't mutate the data, you can create an UnsafeBufferPointer instead, but you'll need to cast the pointer.

let outputFloats = UnsafeBufferPointer<CFloat>(
start: UnsafePointer(output_f),
count: Int(output_f_count))

Since there's no copying, both of those options are very fast. However, they are pointers. If Swift modifies the contents, the C code will see the changed data, and vice-versa. That may or may not be a good thing, depending on your use case, but you definitely want to be aware of it.

If you want to make a copy, you can make a Swift Array very easily like this:

let outputFloatsArray = [CFloat](outputFloats)

Now you have you Swift-side copy in an Array.

As a very closely related thing, if in a C header, output_f were declared as an actual array like this,

extern float output_f[1360*1060];

Then Swift doesn't see a pointer. It sees, believe it or not, a tuple... a great big ugly tuple with a crap-load of CFloat members, which has the benefit of being a value type, but is hard to work with directly because you can't index into it. Fortunately you can work around that:

withUnsafeBytes(of: output_f) 
{
let outputFloats = $0.bindMemory(to: CFloat.self)

// Now within the scope of this closure you can use outputFloats
// just as before.
}
  • Note: You can also use the pointer directly without going through the buffer pointer types, and because you avoid bounds-checking that way, it is a tiny bit faster, but just a very tiny bit, it's more awkward, and well... you lose the error catching benefits of bounds-checking. Plus the buffer pointer types provide all the RandomAccessCollection methods like map, filter, forEach, etc...

Update:

In comments OP said that he had tried this approach but got EXEC_BAD_ACCESS while dereferencing them. Missing is the context of what is happening between obtaining the pointer from output and its being available to Swift.

Given the clue from earlier that it's actually C++, I think output is probably std::vector<float>, and its probably going out of scope before Swift does anything with the pointers, so its destructor is being called, which of course, deletes its internal data pointer. In that case Swift is accessing memory that is no longer valid.

There are two ways to address this. The first is to make sure that output is not cleaned up until after Swift is done with it's data. The other option, is to copy the data in C.

const int capacity = 1360*1060;
float* p = output.data_ptr<float>();

// static_cast because the above template syntax indicates
// this is actually C++, not C.
float* output_f = static_cast<float*>(calloc(capacity, sizeof(float)));
memcpy(output_f, p, capacity * sizeof(float));

Now output can be cleaned up before Swift accesses output_f. Also this makes the copy that was originally asked about much faster that using NSArray. Assuming the C code doesn't use output_f after this, Swift can just take ownership of it. In that case, Swift needs to be sure to call free(outout_f) when it's done.

If the Swift code doesn't care about it being in an actual array, the Unsafe...BufferPointer types will do the job.

However, if an actual Array is desired, this will be yet another copy, and copying the same data twice just to get it in a Swift Array doesn't make sense if it can be avoided. How to avoid it depends on whether C (or Obj-C) is calling Swift, or Swift is calling Obj-C. I'm going to assume that it's Swift calling C. So let's assume that Swift is calling some C function get_floats() defined like this:

extern "C" *float get_floats()
{
const int capacity = 1360*1060;
float* p = output.data_ptr<float>();

// static_cast because the above template syntax indicates
// this is actually C++, not C.
float* output_f = static_cast<float*>(
calloc(capacity, sizeof(float))
);
memcpy(output_f, p, capacity * sizeof(float));

// Maybe do other work including disposing of `output`

return output_f;
}

You want to change the interface so that a pre-allocated pointer is provided as a parameter, along with its capacity.

extern "C" void get_floats(float *output_f, int capacity)
{
float* p = output.data_ptr<float>();

memcpy(output_f, p, capacity * sizeof(float));

// Maybe do other work including disposing of `output`

// can use return for something else now -- maybe error code?
}

On the Swift side, you could allocate pointers, but since you want it in an Array anyway:

var outputFloats = [Array](repeating: 0, count: 1360*1060)

outputFloats.withUnsafeMutableBuffer {
get_floats($0.baseAddress, CInt($0.count))
}

// Now the array is populated with the contents of the C array.

One last thing. The above code makes an assumption that output.data_ptr() points to at least capacity number of floats. Are you sure this is true? Assuming output is std::vector, it would be better to change the memcpy call to:

    const size_t floatsToCopy = std::min(capacity, output.size())
memcpy(output_f, p, floatsToCopy * sizeof(float));

That ensures that you're not reading garbage from the end of real data if it's actually less than capacity. Then you can return floatsToCopy; from get_floats.

Then on the Swift side, it looks like this:

var outputFloats = [Array](repeating: 0, count: 1360*1060)

let floatsCopied = outputFloats.withUnsafeMutableBuffer {
get_floats($0.baseAddress, CInt($0.count))
}

outputFloats.removeLast(
outputFloats.count - Int(floatsCopied),
keepingCapacity: true)

You don't actually have to use the keepingCapacity parameter, but doing so allows you to re-use the array without having to pay for more memory allocations. Just refill out to full capacity before calling get_floats again with the same array. Plus unless your peak memory usage is an issue, keepingCapacity: true is likely faster, and at least no worse, than the default, because without it, Array might choose to reallocate to the smaller size, which internally is an allocation, a copy, and a free, and the whole point was to avoid a copy... but the dynamic memory allocation is the really slow part. Given CPU caches and the way instruction pipelines work, you can do a lot of sequential copying in the time it takes to do a single memory allocation.

Class variables are deallocated after calling function

Your code is sort of a showcase of bad usages when working with Arrays and pointers.

For example:

var complexBuffer1 = DSPSplitComplex(realp: UnsafeMutablePointer(mutating: reals), imagp: UnsafeMutablePointer(mutating: imags))

or:

var kernel:DSPSplitComplex? = DSPSplitComplex(realp: &r, imagp: &im)

DSPSplitComplex holds two pointers for real part and imaginary part separately and does not copy the contents. You should not pass Swift Arrays for such parameters.

And the most critical part in your code is...

var res:Float = 0
var ims:Float = 0
var result:DSPSplitComplex = DSPSplitComplex(realp: &res, imagp: &ims)
vDSP_zvmul(&kernel!, 1, &fft1Input, 1, &result, 1, vDSP_Length(r.count), 1)

vDSP_zvmul generates N (in your code N = vDSP_Length(r.count)) complex numbers, so you need to prepare a region which can hold N elements.

Once you call vDSP_zvmul with your current code, you break whole stack contents which causes what you have experienced:

In the debugger it seems that on the 2nd iteration of this loop, there
are no variables under the 'self' drop down.


You are hiding many parts of your code, so it is very hard to guess what you really want to do, but if I re-write your code in safer manner, it would be something like this:

func convolveInput(realsamples:[Float], imagsamples:[Float]) -> [Float]{
realResult = Array(repeating: [], count: filterbankReal.count)
imagResult = Array(repeating: [], count: filterbankReal.count)
let x = realsamples
let y = imagsamples
var N = x.count
var logN = 16
var fft1Setup = vDSP_create_fftsetup(UInt(logN), FFTRadix(FFT_RADIX2))!

var paddedLength = x.count + filterbankReal.count - 1
var halfPaddedLength = paddedLength/2
var halfKernelLength = kernelLength/2
//setup Complex Buffer 1
var reals = UnsafeMutableBufferPointer<Float>.allocate(capacity: x.count)
defer {reals.deallocate()}
var imags = UnsafeMutableBufferPointer<Float>.allocate(capacity: y.count)
defer {imags.deallocate()}
_ = reals.initialize(from: x)
_ = imags.initialize(from: y)
var complexBuffer1 = DSPSplitComplex(realp: reals.baseAddress!, imagp: imags.baseAddress!)

//Perform FFT on incoming samples
var re = UnsafeMutableBufferPointer<Float>.allocate(capacity: N)
defer {re.deallocate()}
var im = UnsafeMutableBufferPointer<Float>.allocate(capacity: N)
defer {im.deallocate()}
var fft1Input = DSPSplitComplex(realp: re.baseAddress!, imagp: im.baseAddress!)

let fftlength = 10
vDSP_fft_zop(fft1Setup, &complexBuffer1, 1, &fft1Input, 1, UInt(fftlength), Int32(FFT_FORWARD))

//Remove DC from FFT Signal
fft1Input = DSPSplitComplex(realp: re.baseAddress!+1, imagp: im.baseAddress!+1)

for i in 0..<self.fftfilterbankReal.count {
self.fftfilterbankReal[i].withUnsafeMutableBufferPointer {rBuf in
self.fftfilterbankImag[i].withUnsafeMutableBufferPointer {imBuf in
var kernel = DSPSplitComplex(realp: rBuf.baseAddress!, imagp: imBuf.baseAddress!)
var res = UnsafeMutableBufferPointer<Float>.allocate(capacity: rBuf.count)
defer {res.deallocate()}
var ims = UnsafeMutableBufferPointer<Float>.allocate(capacity: rBuf.count)
defer {ims.deallocate()}
var result:DSPSplitComplex = DSPSplitComplex(realp: res.baseAddress!, imagp: ims.baseAddress!)
vDSP_zvmul(&kernel, 1, &fft1Input, 1, &result, 1, vDSP_Length(rBuf.count), 1)
//vDSP_zvmul generates `N` complex numbers,
// I do not understand what you really want to do...
self.realResult[i].append(res[0])
self.imagResult[i].append(ims[0])
}
}
}

//...
}

There may be other parts to fix, but anyway, please try and see what you get.

Extracting vertices from scenekit

The geometry source

When you call geometrySourcesForSemantic: you are given back an array of SCNGeometrySource objects with the given semantic in your case the sources for the vertex data).

This data could have been encoded in many different ways and a multiple sources can use the same data with a different stride and offset. The source itself has a bunch of properties for you to be able to decode the data like for example

  • dataStride
  • dataOffset
  • vectorCount
  • componentsPerVector
  • bytesPerComponent

You can use combinations of these to figure out which parts of the data to read and make vertices out of them.

Decoding

The stride tells you how many bytes you should step to get to the next vector and the offset tells you how many bytes offset from the start of that vector you should offset before getting to the relevant pars of the data for that vector. The number of bytes you should read for each vector is componentsPerVector * bytesPerComponent

Code to read out all the vertices for a single geometry source would look something like this

// Get the vertex sources
NSArray *vertexSources = [geometry geometrySourcesForSemantic:SCNGeometrySourceSemanticVertex];

// Get the first source
SCNGeometrySource *vertexSource = vertexSources[0]; // TODO: Parse all the sources

NSInteger stride = vertexSource.dataStride; // in bytes
NSInteger offset = vertexSource.dataOffset; // in bytes

NSInteger componentsPerVector = vertexSource.componentsPerVector;
NSInteger bytesPerVector = componentsPerVector * vertexSource.bytesPerComponent;
NSInteger vectorCount = vertexSource.vectorCount;

SCNVector3 vertices[vectorCount]; // A new array for vertices

// for each vector, read the bytes
for (NSInteger i=0; i<vectorCount; i++) {

// Assuming that bytes per component is 4 (a float)
// If it was 8 then it would be a double (aka CGFloat)
float vectorData[componentsPerVector];

// The range of bytes for this vector
NSRange byteRange = NSMakeRange(i*stride + offset, // Start at current stride + offset
bytesPerVector); // and read the lenght of one vector

// Read into the vector data buffer
[vertexSource.data getBytes:&vectorData range:byteRange];

// At this point you can read the data from the float array
float x = vectorData[0];
float y = vectorData[1];
float z = vectorData[2];

// ... Maybe even save it as an SCNVector3 for later use ...
vertices[i] = SCNVector3Make(x, y, z);

// ... or just log it
NSLog(@"x:%f, y:%f, z:%f", x, y, z);
}

The geometry element

This will give you all the vertices but won't tell you how they are used to construct the geometry. For that you need the geometry element that manages the indices for the vertices.

You can get the number of geometry elements for a piece of geometry from the geometryElementCount property. Then you can get the different elements using geometryElementAtIndex:.

The element can tell you if the vertices are used a individual triangles or a triangle strip. It also tells you the bytes per index (the indices may have been ints or shorts which will be necessary to decode its data.



Related Topics



Leave a reply



Submit