Search code examples
c++objective-cmetal

How can I store a id<MTLBuffer> inside of c++ code?


I am writing a cross-platform engine to do some rendering. I am trying to create a cross-platform structure that essentially manages a type of geometry that is drawn.

The goal is that there is a c++ class that holds a void* to a block of memory allocated for a buffer and then that pointer is passed into a MTLBuffer or a Vulcan Buffer for use in rendering. As such one of the fields of this class is going to need to be a buffer but in a cross-platform sense.

For a portion of my drawing, the code should look like

func draw() {
   CrossPlatformBuffers* buffs = // preset list
   for (buffer in buffs {
      PlatformSpecificEngine.drawWith((PlatformSpecificBuffer)buffs->buffer)
   }
}

So essentially I need to be able to store my MTLBuffer as a void* within a c++ class. This is confusing to me as I am not exactly sure how c++ plays with objective-c ARC or exactly what id is supposed to mean.

Would I encounter any issues if I just casted the id into a void* and passed it into a c++ class as and later called delete on it?


Solution

  • There are several points you need to consider here:

    Your MTLBuffer object pointer does not point to the object's contents

    MTLBuffer is a Metal framework object that manages a memory buffer on the GPU. The address you have is just the address of that object. For some buffers, Metal provides a way to access the contents of the buffer from the CPU by using the [MTLBuffer contents] method. contents returns a void * that you can directly use to read and write from your buffer, with the following caveats:

    Your MTLBuffer's contents may not always be accessible from the CPU

    It depends on which platform you're on. If you're solely on iOS/tvOS, just create your MTLBuffer with the MTLStorageModeShared storage mode, and you should be good to go -- Metal will ensure the data you see on the CPU is in sync with the GPU's view. On macOS it depends on which GPU you're using so there are some additional subtleties there. For now I'm assuming we're talking iOS/tvOS only.

    There are several ways to combine Objective-C with C++ code

    One option is to create Objective-C++ files (.mm files), and put all your Metal-specific code in a subclass within those .mm files. That would allow you to have the benefit of Objective-C's ARC (Automatic Reference Counting) and still create nice, generic C++ wrappers. In the header file for your Objective-C++ class, you would do something like this:

    class MetalBuffer : GenericBuffer
    {
       private:
    #ifdef __OBJC__
          id <MTLBuffer> metalBuffer;
    #else
          void *internalMetalBuffer;
    #endif
    }
    

    This would let your regular (non-Objective-C++) classes include the Objective-C++ header. I made the "special" managed pointer private to ensure no one tries to access it from outside the Objective-C++ class, as that would obviously be a bad idea.

    If this approach isn't suitable, you could just do everything in C++, but then you'd have to manually track references to your Objective-C objects:

    If you must store your objects in your C++ code as void pointers, you will need manual reference counting

    Objective-C uses ARC to track object usage and to automatically release objects as appropriate. If you're trying to manage the whole thing in C++, you will need to manually manage references to your objects (such as your MTLBuffer and beyond). This is done by telling ARC that you wish to type-cast the managed, Objective-C id objects to regular C pointers.

    After instantiating your MTLBuffer, you can use CFBridgingRetain() on your object, which now lets you store it as a void * (not to be confused with the void * you grabbed that points to the contents of your buffer!) in your C++ class. When you're done using the MTLBuffer, you can call CFRelease() on your void * to free it up. You do not need to worry about freeing the contents buffer -- the underlying memory will be freed automatically once the MTLBuffer object is released (such as when you call CFRelease()).

    Note that you can use CFBridgingRelease() when you need to invoke Objective-C functions that use your MTLBuffer object (such as setFragmentBuffer, etc.) Think of CFBridgingRelease() as a converter that hands your object back to ARC, but note that it includes a manual release, which means that once Metal is done with your object, it will automatically be released.

    If you want your object to live beyond the current Metal request, you should retain another pointer to it using CFBridgingRetain().

    Again, this is a last resort -- I would not recommend taking this route.

    Good luck!