Я пишу кроссплатформенный движок для рендеринга. Я пытаюсь создать кроссплатформенную структуру, которая по существу управляет типом геометрии, которая рисуется.
Цель состоит в том, чтобы существовал класс c ++, который содержит void * для блока памяти, выделенного для буфера, а затем этот указатель передается в MTLBuffer или Vulcan Buffer для использования при рендеринге. Таким образом, одно из полей этого класса должно быть буфером, но в кроссплатформенном смысле.
Для части моего рисунка код должен выглядеть так
func draw() {
CrossPlatformBuffers* buffs = // preset list
for (buffer in buffs {
PlatformSpecificEngine.drawWith((PlatformSpecificBuffer)buffs->buffer)
}
}
По сути, мне нужно иметь возможность хранить свой MTLBuffer как void * в классе c ++. Это сбивает с толку меня, так как я не совсем уверен, как c ++ играет с target-c ARC или что именно должен означать id.
Буду ли я сталкиваться с какими-либо проблемами, если я просто добавлю идентификатор в void * и передам его в класс c ++, как позже назвал delete для него?
Здесь нужно учесть несколько моментов:
Ваш указатель на объект MTLBuffer делает не указать на содержимое объекта
MTLBuffer
это металлический каркасный объект, который управляет буфером памяти на GPU. Адрес, который у вас есть, это просто адрес этого объекта. Для некоторых буферов Metal предоставляет способ доступа к содержимому буфера из CPU, используя [MTLBuffer contents]
метод. contents
возвращает void *
что вы можете напрямую использовать для чтения и записи из вашего буфера со следующими оговорками:
Содержимое вашего MTLBuffer не всегда доступно из CPU
Это зависит от того, на какой платформе вы находитесь. Если вы работаете исключительно на iOS / tvOS, просто создайте свой MTLBuffer
с MTLStorageModeShared
режим хранения, и вы должны быть в порядке — Metal обеспечит синхронизацию данных, которые вы видите на процессоре, с видом графического процессора. В MacOS это зависит от того, какой графический процессор вы используете, поэтому здесь есть некоторые дополнительные тонкости. Сейчас я предполагаю, что мы говорим только о iOS / tvOS.
Есть несколько способов объединить Objective-C с кодом C ++
Один из вариантов — создать файлы Objective-C ++ (файлы .mm) и поместить весь свой специфичный для Metal код в подкласс внутри этих файлов .mm. Это позволило бы вам воспользоваться преимуществами ARC (автоматического подсчета ссылок) Objective-C и по-прежнему создавать красивые, обертки C ++. В файле заголовка для вашего класса Objective-C ++ вы должны сделать что-то вроде этого:
class MetalBuffer : GenericBuffer
{
private:
#ifdef __OBJC__
id <MTLBuffer> metalBuffer;
#else
void *internalMetalBuffer;
#endif
}
Это позволит вашим обычным (не Objective-C ++) классам включать заголовок Objective-C ++. Я сделал «специальный» управляемый указатель private
чтобы никто не пытался получить к нему доступ из-за пределов класса Objective-C ++, поскольку это, очевидно, было бы плохой идеей.
Если этот подход не подходит, вы можете просто делать все на C ++, но тогда вам придется вручную отслеживать ссылки на ваши объекты Objective-C:
Если вы должны хранить ваши объекты в коде C ++ как пустые указатели, вам потребуется ручной подсчет ссылок
Objective-C использует ARC для отслеживания использования объекта и автоматического освобождения объектов по мере необходимости. Если вы пытаетесь управлять всем этим в C ++, вам нужно будет вручную управлять ссылками на ваши объекты (например, ваши MTLBuffer
и не только) Это делается сообщением ARC, что вы хотите набрать управляемый Objective-C id
возражает против регулярных указателей Си.
После создания вашего экземпляра MTLBuffer
, ты можешь использовать CFBridgingRetain()
на вашем объекте, который теперь позволяет хранить его как void *
(не путать с void *
вы схватили это указывает на содержание вашего буфера!) в вашем классе C ++. Когда вы закончите, используя MTLBuffer
, ты можешь позвонить CFRelease()
на ваше void *
чтобы освободить это. Вам не нужно беспокоиться об освобождении contents
буфер — основная память будет освобождена автоматически, как только MTLBuffer
объект освобожден (например, когда вы звоните CFRelease()
).
Обратите внимание, что вы можете использовать CFBridgingRelease()
когда вам нужно вызвать функции Objective-C, которые используют ваш MTLBuffer
объект (такой как setFragmentBuffer
и т. д.) Подумайте о CFBridgingRelease()
как конвертер, который возвращает ваш объект обратно в ARC, но учтите, что он включает ручное освобождение, а это означает, что как только Metal завершит работу с вашим объектом, он будет автоматически освобожден.
Если вы хотите, чтобы ваш объект жил за пределами текущего запроса Metal, вам следует сохранить другой указатель на него, используя CFBridgingRetain()
,
Опять же, это последнее средство — я бы не рекомендовал этот маршрут.
Удачи!
Мне кажется, что вы используете неправильную абстракцию. Это часто решается путем использования базового класса, который реализует то, что вы хотите сделать на высоком уровне, и наличия подкласса для каждой платформы, которая выполняет определенную работу. Так что в вашем случае у вас может быть что-то вроде базового класса Renderer:
class Renderer {
public:
Renderer();
~Renderer();
virtual void* allocateBuffer(const size_t numBytes) = 0;
virtual void renderWorld() = 0;
... etc.
};
Тогда у вас будет 2 платформо-зависимых класса: VulkanRenderer
а также MetalRenderer
:
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.
Ни один из вашего другого кода не должен иметь дело ни с MetalRenderer
или же VulkanRenderer
но вместо этого просто Renderer
,