Глубинный буфер Direct2D

Мне нужно нарисовать список фигур, и я использую Direct2D. Я получаю список фигур из файла. Список сортируется, и порядок элементов внутри файла представляет порядок, в котором будут нарисованы эти фигуры. Итак, если, например, файл указывает два прямоугольника в одном и том же положении и с одинаковыми размерами, будет видна только вторая (поскольку первая будет перезаписана).

Учитывая мой список фигур, я перехожу к его рисунку следующим образом:

list<Shape> shapes;

for (const auto& shape : shapes)
   shape.draw();

Нетрудно видеть, что если у меня есть две формы, я не могу инвертировать порядок операций рисования, и это означает, что я должен быть уверен, что shape2 будет всегда рисоваться после shape1 и т.д. Следуя за тем, что я не могу использовать несколько потоков для рисования фигур, и это огромный недостаток в плане производительности.

Я читал, что Direct3D поддерживает буфер глубины (или z-buffer), который определяет для каждого пикселя его z-координату, так что будут рисоваться только "видимые" пиксели (ближе к зрителю), независимо от порядка, в котором фигуры рисуются. И у меня есть информация о глубине каждой фигуры, когда я читаю файл.

Есть ли способ использовать буфер глубины в Direct2D или подобный метод, который позволяет мне использовать несколько потоков для рисования моих фигур?

Ответ 1

Есть ли способ использовать буфер глубины в Direct2D или аналогичный техника, которая позволяет мне использовать несколько потоков для рисования формы?

Ответ здесь нет. Хотя библиотека Direct2D построена поверх Direct3D, она не предоставляет пользователю такую ​​функцию через API, поскольку примитивы, которые вы можете рисовать, описываются только двумерными координатами. Последний примитив, который вы рисуете для цели рендеринга, становится видимым, поэтому не проводится глубокое тестирование. Кроме того, буфер глубины в Direct3D не имеет большого отношения к многопоточности на стороне процессора.

Также обратите внимание, что даже если вы выдаете команды рисования с использованием нескольких потоков, они будут сериализованы драйвером Direct3D и выполняться последовательно. Некоторые новые графические API, такие как Direct3D 12 и Vulkan, предоставляют многопоточные драйверы, которые позволяют эффективно использовать различный контент из разных потоков, но они имеют более высокую сложность.

Итак, в конце концов, если вы придерживаетесь Direct2D, вы останетесь с возможностью рисования каждой фигуры последовательно с использованием одного потока.

Но что можно сделать, так это то, что вы можете исключить эффективно окклюдированные фигуры, проверяя окклюзию каждой фигуры против всех остальных. Таким образом, окклюдированные формы могут быть отброшены из списка и никогда не отображаться вообще. Трюк здесь заключается в том, что некоторые формы не заполняют свои границы полностью, из-за прозрачных областей (например, текста) или если фигура является сложным многоугольником. Такие формы не могут быть легко протестированы или потребуются более сложные алгоритмы.

Итак, вам нужно выполнить итерацию всех фигур, а если текущая форма - это прямоугольник, тогда выполните тестирование окклюзии со всеми предыдущими формами bounds rects.

Следующий код следует рассматривать как псевдокод, он предназначен только для демонстрации идеи.

#define RECTANGLE 0
#define TEXT      1
#define TRIANGLE  2
//etc

typedef struct {
    int type; //We have a type field
    Rect bounds_rect; //Bounds rect
    Rect coordinates; //Coordinates, which count vary according to shape type
    //Probably you have many other fields here
} Shape;

//We have all shapes in a vector
std::vector<Shape> shapes;

Итерировать все фигуры.

for (int i=1; i<shapes.size; i++) {
  if(shape[i].type != RECTANGLE) {
    //We will not perform testing if the current shape is not rectangle.
    continue; 
  }

  for(int j=0; j<i; j++) {
    if(isOccluded(&shape[j], &shape[i])) {
      //shape[j] is totally invisible, so remove it from 'shapes' list
    }
  }
}

Проверка окклюзии - это что-то вроде этого

bool isOccluded(Shape *a, Shape *b) {
  return (a.bounds_rect.left > b.coordinates.left && a.bounds_rect.right < b.coordinates.right &&
          a.bounds_rect.top > b.coordinates.to && a.bounds_rect.bottom < b.coordinates.bottom);
}

И вам не нужно перебирать все фигуры с помощью одного потока, вы можете создавать несколько потоков для выполнения тестов для разных частей списка фигур. Конечно, вам понадобится некоторая технология блокировки, такая как мьютекс при удалении фигур из списка, но это еще одна тема.

Ответ 2

Буфер глубины используется для удаления примитивов, которые будут закрыты чем-то перед ним в трехмерном пространстве, сохраняя время перерисовки, не беспокоясь о вещах, которые в любом случае не будут видны. Если вы думаете о сцене с высокой тонкой свечой перед мячом, обращенным к камере, весь шар не нарисован, а затем свеча нарисована поверх него, только видимые стороны мяча. Таким образом, порядок рисования не имеет значения.

Я не слышал об использовании буфера глубины в D2D, поскольку он несколько бессмыслен; все нарисовано на одну плоскость в D2D, как может быть что-то впереди или за чем-то еще? API может поддержать его, но я сомневаюсь в этом, поскольку он не имеет абстрактного смысла. Информация о глубине каждой фигуры - это просто порядок ее рисования по существу, который у вас уже есть.

Вместо того, что вы могли бы сделать, разделите и распределите свои фигуры на свои потоки при сохранении порядка, т.е.

t1 { shape1, shape2, shape3 } = shape123
t2 { shape4, shape5, shape6 } = shape456
...

И нарисуйте фигуры на новом объекте (но не на backbuffer), в зависимости от вашего класса формы вы, возможно, сможете представить результат как форму. Это оставит вас с t различными формами, которые все еще в порядке, но были вычислены параллельно. Затем вы можете постепенно составить свой окончательный результат, вычерчивая результаты по порядку, т.е.

t1 { shape123, shape456, shape789 }
t2 { shape101112, shape131415 }

t1 { shape123456789, shape101112131415 } = final shape

Теперь у вас есть окончательная форма, которую вы можете просто нарисовать, как обычно