How to cast `UnsafePointer<T>?` to `UnsafePointer<T?>`?

As titled, I have the following function parameter:

waitSemaphores: [VkSemaphore?]? = nil

but the C struct which I'm gonna build requires:

UnsafePointer<VkSemaphore?>?

At the moment this is what I'm doing:

        var pWaitSemaphores: UnsafePointer<VkSemaphore?>?
        if let waitSemaphores = waitSemaphores {
            pWaitSemaphores = UnsafePointer(waitSemaphores)
        }

which is based on UnsafePointer<VkSemaphore>? though and therefore doesn't work:

error: cannot assign value of type 'UnsafePointer<VkSemaphore?>' (aka 'UnsafePointer<Optional< OpaquePointer>>') to type 'UnsafePointer< VkSemaphore>?' (aka 'Optional<UnsafePointer< OpaquePointer>>')

I tried to cast with as, but it didn't work out

I am not sure here, and I learned that playing around with UnsafePointers in Playgrounds is a good way to lose all of your sandbox code.

If you change
pWaitSemaphores = UnsafePointer(waitSemaphores)
to
pWaitSemaphores = Optional<UnsafePointer>(waitSemaphores)
does that do what you want?

1 Like
pWaitSemaphores = waitSemaphores?.withUnsafeBufferPointer { $0.baseAddress }

That isn't legal. "The pointer argument is valid only for the duration of the method’s execution."

1 Like

Ah, good point. I forgot about that. How about

if var waitSemaphores = waitSemaphores {
    let pWaitSemaphores = UnsafeBufferPointer(start: &waitSemaphores, count: waitSemaphores.count).baseAddress
}

Same problem. The reference taken by & is only guaranteed to last for the duration of that call.

withUnsafeBufferPointer is the right direction, but you have to "do all your work" within its closure, rather than smuggling it out

1 Like

Hmm, I guess that would depend on the using cases. If the pointer is just going to be used within the scope, then it is fine. Otherwise the proper way would be allocating its own buffer and copy all the bytes there and manage its lifecycle manually.

ok, then this is what I'm trying to do at the moment

public extension Optional where Wrapped: Collection {

    public func withOptionalUnsafeBufferPointer<T, R>(_ body: (UnsafePointer<T>?) -> R) -> R {
        (self as? [T])?.withUnsafeBufferPointer(body) ?? body(nil)
    }
}

in order to type:

static func SubmitInfo(waitSemaphores: [VkSemaphore]? = nil) -> VkSubmitInfo {
    waitSemaphores.withOptionalUnsafeBufferPointer { pWaitSemaphores in
        VkSubmitInfo(
            ...
            waitSemaphoreCount: UInt32(waitSemaphores?.count ?? 0),
            pWaitSemaphores: pWaitSemaphores,
            ...)
        }
    }

but I get:

error: cannot convert value of type '(UnsafePointer?) -> R' to expected argument type '(UnsafeBufferPointer<_>) -> _'

Your code calls withUnsafeBufferPointer(body), but declares that body accepts an UnsafePointer, not an UnsafeBufferPointer. This is your type mismatch.

More generally, you've written code that is doing altogether too much runtime type checking. You're extending Optional<Collection> and then casting to Array, so your code really only works when Self is [T]. Let's just embrace that. You've also got exclusive access violations floating around in your code too, which won't help.

Here's the code you want:

extension Optional {
    public func withOptionalUnsafeBufferPointer<Element, Result>(_ body: (UnsafeBufferPointer<Element>?) throws -> Result) rethrows -> Result where Wrapped == Array<Element> {
        if let wrapped = self {
            return try wrapped.withUnsafeBufferPointer(body)
        } else {
            return try body(nil)
        }
    }
}

Then, your SubmitInfo function becomes:

static func SubmitInfo(waitSemaphores: [VkSemaphore]? = nil) -> VkSubmitInfo {
    waitSemaphores.withOptionalUnsafeBufferPointer { pWaitSemaphores in
        VkSubmitInfo(
            ...
            waitSemaphoreCount: UInt32(pWaitSemaphores?.count ?? 0),
            pWaitSemaphores: pWaitSemaphores.flatMap { $0.baseAddress },
            ...)
    }
}

Hope that helps!

1 Like

error: cannot convert value of type 'UnsafeBufferPointer< VkSemaphore>?' (aka 'Optional<UnsafeBufferPointer< OpaquePointer>>') to expected argument type 'UnsafePointer<VkSemaphore?>?' (aka 'Optional<UnsafePointer<Optional< OpaquePointer>>>')

If I pass instead

pWaitSemaphores: pWaitSemaphores?.baseAddress,

then I get:

error: cannot convert value of type 'UnsafePointer< VkSemaphore>?' (aka 'Optional<UnsafePointer< OpaquePointer>>') to expected argument type 'UnsafePointer< VkSemaphore?>?' (aka 'Optional<UnsafePointer<Optional< OpaquePointer>>>')

How can your C function require a Swift optional type? Swift optionals are not representable in C.

Honestly I have no idea. I'll push later so you can try yourself if you would like to

ps: it might be related to CLion, though

An aside to the other comments, but I feel like you're probably approaching this the wrong way. You probably shouldn't be trying to wrap the Vulkan structures; instead, you should be trying to wrap the function calls and provide custom struct/class types. In Vulkan written in C, the pattern is to declare a struct on the stack and then pass it to a Vulkan function, like this:

VkSubmitInfo submitInfo = { VK_STRUCTURE_TYPE_SUBMIT_INFO, ... };
submitInfo.pWaitSemaphores = &waitSemaphores;
vkQueueSubmit(queue, 1, &submitInfo, fence)

However, if you do that in Swift, you end up with a 'pyramid of doom'; every time you'd take the address of a stack variable in C with & you need a withUnsafePointer(to:) call in Swift. How I'd approach this would be to instead declare a VulkanSubmitInfo struct as you would in Swift:

struct VulkanSubmitInfo {
    var waitSemaphores : [VulkanSemaphore]
    var waitDestinationStageMasks : [VkPipelineStateFlags]
    var commandBuffers : [VulkanCommandBuffer]
    var signalSemaphores : [VulkanSemaphore]
}

and then have a function that provides a closure with a VkSubmitInfo. This function would be the only place that contains the 'pyramid of doom' per type:

extension VulkanSubmitInfo {
    func withSubmitInfo(_ perform: (inout VkSubmitInfo) -> Void) {
        var submitInfo = VkSubmitInfo()
            ... // structure type, pNext
            
        // Map to the underlying VkSemaphore? for each VulkanSemaphore.
        var vkSemaphores = self.semaphores.map { $0.vkSemaphore as VkSemaphore? }
        vkSemaphores.withUnsafeBufferPointer { waitSemaphores in
            submitInfo.pWaitSemaphores = waitSemaphores.baseAddress
            submitInfo.waitSemaphoreCount = UInt32(waitSemaphores.count)
            
            // nest more for commandBuffers, signal semaphores etc.
            perform(&submitInfo)
        }
        
    }
}

Then, you can declare something like:

class VulkanQueue {
    var queue : VkQueue
    
    // You can write a simple method that submits only one VulkanSubmitInfo
    func submit(_ submitInfo: VulkanSubmitInfo, fence: VkFence) {
        submitInfo.withSubmitInfo { vkQueueSubmit(queue, 1, $0, fence) }
    }
    
    // You can also write a function that deals with arrays recursively
    // by using an inner helper function to traverse the list.
    // If you've ever used Haskell or similar languages this pattern
    // should be familiar.
    // Note that this nested style means we never exit the `withSubmitInfo` closures,
    // which means that they are guaranteed to stay valid.
    func submit(_ submitInfos: [VulkanSubmitInfo], fence: VkFence) {
        var vkSubmitInfos = [VkSubmitInfo]()
        func withSubmitInfos(_ s: ArraySlice<VulkanSubmitInfo>) {
            if let first = s.first {
                first.withSubmitInfo {
                    vkSubmitInfos.append($0)
                    withSubmitInfos(s.dropFirst())
                }
            } else {
                vkQueueSubmit(self.queue, vkSubmitInfos.count, &vkSubmitInfos, fence) 
            }
        }
        
        withSubmitInfos(submitInfos[submitInfos.indices])
    }
}

I will add that it gets pretty tricky to make an idiomatic Vulkan wrapper API in Swift; you need to think in terms of variable lifetimes, nested closures, and recursive function calls to manage those closures.

Optional pointers are representable in C; e.g. int function(void *_Nullable ptr) is func function(_ ptr: UnsafeMutableRawPointer?) -> Int32 in Swift.

Sure, but this type isn't: UnsafePointer< VkSemaphore?>?' (aka 'Optional<UnsafePointer<Optional< OpaquePointer>>>, which is what's in the above warning.

OpaquePointers (which are how VkSemaphore is imported) can be nullable like any other pointer:

typedef struct VkSemaphore VkSemaphore;
int function(VkSemaphore *_Nullable ptr);

// is imported as:
func function(_ ptr: OpaquePointer?) -> Int32

Ah yeah, good catch. In this case, we can also do a slight amendment to the above patch I proposed to offer optionals, but I'm not sure we need it.

I'd like to offer comfortable "construction" functions (this has been proved to play quite nicely on Kotlin), where the implicit parameters are already set by the wrapper (sType, sometimes pNext must be null, or some flag must be zero) and the rest is set to the most common value, so that the user has to simply time what matters.

I thought about that, and given the high compatibility with C, I'd like very much to exploit that.
So I can simply pass for example reference to structs with &, or String to UnsafePointer<Int8> (I admit there are still some issues, mostly because my inexperience, but most of the time it works pretty well)

For example:

    static func ApplicationInfo(
            applicationName: String? = nil,
            applicationVersion: Int = 0,
            engineName: String? = nil,
            engineVersion: Int = 0,
            apiVersion: Int = 0) -> VkApplicationInfo {

        VkApplicationInfo(
                sType: VK_STRUCTURE_TYPE_APPLICATION_INFO,
                pNext: nil,
                pApplicationName: applicationName,
                applicationVersion: UInt32(applicationVersion),
                pEngineName: engineName,
                engineVersion: UInt32(engineVersion),
                apiVersion: UInt32(apiVersion))
    }

This is exactly what will open the Pandora box.. because then I'd have to replicate a completely Swift version of all the C structs in order to being usable, and I'd like to avoid that.

The question is, when I call ApplicationInfo(applicationName: "whatever") cited previously, is pApplicationName still valid when the function returns?

From my tests yes, but maybe it's just a case and that's illegal..

It might work with constant strings, but it’s invalid Swift and undefined behaviour. Any time you’re passed a pointer within a closure you must assume that pointer will be invalid as soon is you leave the closure.

As far as I understood, Swift strings have an underlying utf8 representation exactly for C interoperability.

So, if I call this

        var appInfo = vk.ApplicationInfo(applicationName: "Hello Triangle", applicationVersion: 1)

        var createInfo = vk.InstanceCreateInfo(applicationInfo: &appInfo)

        var instance: VkInstance = try! vk.createInstance(&createInfo)

applicationName get automatically passed to pApplicationName and it should be valid for all the rest of the code, since it's inside the same function.

Am I wrong?

Terms of Service

Privacy Policy

Cookie Policy