Я только начинаю работать с металлом, и мне трудно понять некоторые базовые вещи c. Я читал целую кучу веб-страниц о металле, прорабатывал примеры Apple и так далее, но пробелы в моем понимании остаются. Я думаю, что мой ключевой момент путаницы заключается в следующем: как правильно обращаться с буферами вершин и как узнать, когда их можно безопасно использовать повторно? Эта путаница проявляется несколькими способами, как я опишу ниже, и, возможно, к различным проявлениям моей путаницы нужно подходить по-разному.
Чтобы быть более точным c, я использую MTKView подкласс в Objective- C на macOS для отображения очень простых 2D-фигур: общий кадр для вида с цветом фона внутри, 0+ прямоугольных angular подкадров внутри этого общего кадра с другим цветом фона внутри них, а затем 0 + плоские квадраты разных цветов внутри каждого подрамника. Моя вершинная функция - просто простое преобразование координат, а моя фрагментная функция просто проходит через цвет, который она получает, на основе демонстрационного приложения Apple в виде треугольника. У меня это работает нормально для одного подкадра с одним квадратом. Пока все хорошо.
Есть несколько вещей, которые меня озадачивают.
Один: я мог бы спроектировать свой код так, чтобы он целиком отображался с помощью одного буфера вершин и одного вызова drawPrimitives:
, рисуя все (под) рамки и квадраты в одном большом взрыве. Однако это не оптимально, поскольку нарушает инкапсуляцию моего кода, в котором каждый подкадр представляет состояние одного объекта (объекта, который содержит квадраты 0+); Я бы хотел, чтобы каждый объект отвечал за отрисовку своего содержимого. Поэтому было бы неплохо, чтобы каждый объект устанавливал буфер вершин и выполнял свой собственный вызов drawPrimitives:
. Но поскольку объекты будут отрисовываться последовательно (это однопоточное приложение), я хотел бы повторно использовать один и тот же буфер вершин во всех этих операциях рисования, вместо того чтобы каждый объект должен был выделять и владеть отдельным буфером вершин. Но я могу сделать это? После того, как я вызову drawPrimitives:
, я предполагаю, что содержимое буфера вершин должно быть скопировано в графический процессор, и я предполагаю (?), Что это не делается синхронно, поэтому было бы небезопасно немедленно начинать модифицировать буфер вершин для рисования следующего объекта. Итак: как я узнаю, когда Metal завершит работу с буфером, и я смогу начать его изменение снова?
Два: Даже если у # 1 есть четко определенный ответ, такой, что я могу блокировать, пока Metal не закончится с буфер и затем начните изменять его для следующего вызова drawPrimitives:
, это разумный дизайн? Я предполагаю, что это будет означать, что мой поток ЦП будет постоянно блокироваться, чтобы ждать передачи памяти, что не очень хорошо. Так что это в значительной степени подталкивает меня к дизайну, где каждый объект имеет свой собственный буфер вершин?
Три: ОК, предположим, что у каждого объекта есть свой собственный буфер вершин, или я выполняю один рендеринг "большого взрыва" всего этого с одним большим буфером вершин (я думаю, этот вопрос относится к обоим проектам). После того, как я вызову presentDrawable:
и затем commit
в моем буфере команд, мое приложение go отключится и выполнит небольшую работу, а затем попытается обновить отображение, поэтому мой код для рисования теперь выполняется снова. Я хотел бы повторно использовать буферы вершин, которые я выделил ранее, перезаписывая данные в них, чтобы сделать новое, обновленное отображение. Но опять же: как мне узнать, когда это безопасно? Насколько я понимаю, тот факт, что commit
вернулся в мой код, не означает, что Metal уже завершил копирование моих вершинных буферов в GPU, и в общем случае я должен предположить, что это может занять произвольно много времени, поэтому может быть еще не сделано, когда я повторно введу свой код для рисования. Что правильно сказать? И снова: должен ли я просто блокировать ожидание, пока они не станут доступны (однако я должен это делать), или у меня должен быть второй набор буферов вершин, который я могу использовать в случае, если Metal все еще занят первым набором? (Похоже, что это просто выталкивает проблему вниз, так как, когда мой код рисования введен для третьего обновления, оба ранее использованных набора буферов могут быть еще недоступны, верно? Так что тогда я мог бы добавить третий набор буферов вершин, но затем четвертое обновление ...)
Четыре: для рисования кадра и подкадров, я хотел бы просто написать повторно используемая функция типа «drawFrame», которую могут вызывать все, но я немного озадачен правильным дизайном. С OpenGL это было легко:
- (void)drawViewFrameInBounds:(NSRect)bounds
{
int ox = (int)bounds.origin.x, oy = (int)bounds.origin.y;
glColor3f(0.77f, 0.77f, 0.77f);
glRecti(ox, oy, ox + 1, oy + (int)bounds.size.height);
glRecti(ox + 1, oy, ox + (int)bounds.size.width - 1, oy + 1);
glRecti(ox + (int)bounds.size.width - 1, oy, ox + (int)bounds.size.width, oy + (int)bounds.size.height);
glRecti(ox + 1, oy + (int)bounds.size.height - 1, ox + (int)bounds.size.width - 1, oy + (int)bounds.size.height);
}
Но с металлом я не уверен, что такое хороший дизайн. Я предполагаю, что функция не может просто иметь свой собственный маленький буфер вершин, объявленный как локальный массив stati c, в который она выбрасывает вершины и затем вызывает drawPrimitives:
, потому что, если она вызывается дважды подряд, Metal может еще не иметь скопировал данные вершины из первого вызова, когда второй вызов хочет изменить буфер. Я, очевидно, не хочу выделять новый буфер вершин каждый раз, когда вызывается функция. Я мог бы сделать так, чтобы вызывающая сторона передавала буфер вершин для использования функции, но это только выдвигает проблему на уровень; как тогда вызывающая сторона справится с этой ситуацией? Возможно, я мог бы сделать так, чтобы функция добавляла новые вершины в конец растущего списка вершин в буфере, предоставленном вызывающей стороной; но это, кажется, либо заставляет весь рендер полностью заранее спланировать (так что я могу предварительно выделить большой буфер правильного размера, чтобы он соответствовал всем вершинам, которые будут рисовать все), что требует кода рисования верхнего уровня, чтобы каким-то образом знать, как много вершин, каждый объект будет в конечном итоге генерировать (что нарушает инкапсуляцию), или создать проект, в котором у меня есть расширяющийся буфер вершин, который по-настоящему получает c ', когда его емкость оказывается недостаточной. Я знаю, как делать эти вещи; но никто из них не чувствует себя хорошо. Я борюсь с тем, каков правильный дизайн, потому что я не очень хорошо понимаю модель памяти Metal, я думаю. Любой совет? Извиняюсь за очень длинный многочастный вопрос, но я думаю, что все это сводится к тому же основанию c непониманию.