如何在c ++代码中存储id <mtlbuffer>?

时间:2018-12-26 21:03:28

标签: c++ objective-c metal

我正在编写一个跨平台引擎来进行渲染。我正在尝试创建一个跨平台结构,该结构本质上管理绘制的几何类型。

目标是存在一个c ++类,该类将void *保留给分配给缓冲区的内存块,然后将该指针传递到MTLBuffer或Vulcan Buffer中以进行渲染。因此,此类的一个字段将需要是一个缓冲区,但必须是跨平台的。

对于我的工程图的一部分,代码应类似于

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

所以本质上我需要能够将我的MTLBuffer存储为c ++类中的void *。这让我感到困惑,因为我不确定c ++如何与Objective-C ARC配合使用,或者id到底意味着什么。

如果我将id强制转换为void *并将其传递给c ++类,然后在其上调用delete,我会遇到任何问题吗?

2 个答案:

答案 0 :(得分:5)

这里有几点需要考虑:

您的MTLBuffer对象指针 not 指向对象的内容

MTLBuffer是一个Metal框架对象,用于管理GPU上的内存缓冲区。您拥有的地址就是该对象的地址。对于某些缓冲区,Metal提供了一种使用[MTLBuffer contents]方法从CPU访问缓冲区内容的方法。 contents返回一个void *,您可以直接使用它来从缓冲区读取和写入,但要注意以下几点:

您的MTLBuffer的内容可能并不总是可以通过CPU访问

这取决于您所使用的平台。如果您仅在iOS / tvOS上运行,只需使用MTLBuffer存储模式创建MTLStorageModeShared,然后就可以开始使用-Metal将确保您在CPU上看到的数据是同步的与GPU的视图。在macOS上,这取决于您所使用的GPU,因此那里还有一些其他细节。现在,我假设我们只在谈论iOS / tvOS。

有几种将Objective-C与C ++代码结合的方式

一种选择是创建Objective-C ++文件(.mm文件),并将所有特定于Metal的代码放在这些.mm文件中的子类中。这将使您能够受益于Objective-C的ARC(自动引用计数),并且仍然可以创建漂亮的通用C ++包装器。在Objective-C ++类的头文件中,您将执行以下操作:

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

这将使您的常规(非Object-C ++)类包含Objective-C ++头文件。我制作了“特殊”托管指针private以确保没有人尝试从Objective-C ++类外部访问它,因为这显然不是一个好主意。

如果这种方法不合适,则可以使用C ++进行所有操作,但随后必须手动跟踪对Objective-C对象的引用:

如果必须将对象作为空指针存储在C ++代码中,则需要手动进行引用计数

Objective-C使用ARC来跟踪对象使用情况并适当地自动释放对象。如果您要管理C ++中的全部内容,则需要手动管理对您对象的引用(例如MTLBuffer及其他对象)。这是通过告诉ARC您希望将托管的Objective-C id对象类型转换为常规C指针来完成的。

实例化MTLBuffer之后,可以在对象上使用CFBridgingRetain(),现在可以将其存储为void *(不要与void *混淆)抓住了指向C ++类中缓冲区的内容!使用完MTLBuffer后,可以在CFRelease()上调用void *释放它。您无需担心释放contents缓冲区的情况-释放MTLBuffer对象后(例如,调用CFRelease()时),基础内存将自动释放。

请注意,当您需要调用使用CFBridgingRelease()对象(例如MTLBuffer等)的Objective-C函数时,可以使用setFragmentBuffer作为将您的对象交还给ARC的转换器,但是请注意,它包括手动释放,这意味着一旦对您的对象完成了Metal操作,它将自动释放。

如果您希望对象超出当前的Metal请求,则应使用CFBridgingRelease()保留指向该对象的另一个指针。

同样,这是不得已的做法-我不建议您选择这条路线。

祝你好运!

答案 1 :(得分:1)

在我看来,您使用的是错误的抽象。这通常可以通过使用一个基类来解决,该基类可以高层地实现您想要做的事情,并为每个平台执行特定的工作提供一个子类。因此,在您的情况下,您可能会有类似Renderer基类的内容:

class Renderer {
    public:
        Renderer();
        ~Renderer();

        virtual void* allocateBuffer(const size_t numBytes) = 0;
        virtual void renderWorld() = 0;
        ... etc.
};

那么您将拥有2个特定于平台的类:VulkanRendererMetalRenderer

class VulkanRenderer: public Renderer {
    public:
        VulkanRenderer();
        ~VulkanRenderer();

        virtual void* allocateBuffer(const size_t numBytes);
        virtual void renderWorld();
        ... etc.
};

class MetalRenderer: public Renderer {
    public:
        MetalRenderer();
        ~MetalRenderer();

        virtual void* allocateBuffer(const size_t numBytes);
        virtual void renderWorld();
        ... etc.
};

您的MetalRenderer类的实现文件将是.mm文件而不是.cpp文件,表明它是一个Objective-C ++文件,并允许C ++对象包含Objective-C对象。

您的其他任何代码都不应处理MetalRendererVulkanRenderer,而只能处理Renderer