В предыдущих статьях мы с вами рассмотрели разные аспекты создания GUI, определились с проблемами и рассмотрели возможные пути их решения, теперь самое время собрать это все в "рубаху".
С первой статьи цикла уже прошло много времени, потому для начала я напомню о чем шла речь и основные выводы:
Первый раз о проблеме вывода низкополигональных объектов мы заговорили в статье "Рисуем "Hello World!", где было решено что множество однотипных низкополигональных объектов (отдельных букв текста) лучше объединять в один буфер или текстуру, причину этого мы разобрали в статье "Сколько стоит полигон", где на практике убедились в том, что вывод лишнего полигона без растеризации практически ничего не стоит и существенно дешевле лишнего бинда буфера, а если и приходится это делать, то лучше группировать их в пакеты по сотне полигонов. Ну и в последней статье, "Рисуем GUI. Часть первая, немного тестов", мы в очередной раз убедились что архитектура современных видеокарт ужасно плохо стыкуется с низкополигональной графикой, даже в плане задания трансформаций. Так мы выяснили, что при работе с отдельными квадами можно получить стократный прирост производительности, просто отказавшись от конвеера трансформаций OpenGL, заменив его прямой модификацией вершинных данных.
Осталось решить одну маленькую проблему - как при выводе пакета данных исключить отрисовку какой-то части буфера VBO, чтоб иметь возможность воспользоваться всеми описанными преимуществами. Можно легко отрезать начало или конец буфера, но это делу не поможет, если вы попытаетесь нарисовать нечто подобное:
Какие есть способы - их несколько, частично они уже упоминались в предыдущих статьях:
Коллапс, она же свертка, или сжатие объекта в материальную точку, он же "вырожденный полигон", он же "полигон с нулевой площадью" - процесс, при котором на выходе мы получим полигон без растеризируемых пикселей. Теперь вспоминаем - не встречалось ли нам это определение раньше? Встречалось, и даже выделено в начале этой статьи. Другими словами, если мы "свернем" полигон, то наша задача превратится в задачу, описанную в статье "Сколько стоит полигон", а из тестов мы уже знаем, что это очень дешевая процедура.
Сама процедура свертки квада очень простая - мы, используя методику описанную в предыдущей статье, просто обновляем координаты квада, указывая одинаковое значение для всех 4-х вершин (к примеру координату (0,0,0)). Свернутый полигон во-первых не будет отображаться (реализация свойства Visible), во-вторых - не требует особых ресурсов, что нам и требовалось.
Подытожим сказанное - для эффективного рендеринга GUI нужно выполнить несколько условий:
В приведенном примере выводится примерно 700 элементов управления, что соответствует примерно 4к полигонов. На вывод этого множества потребовалось всего 2 команды рисования, причем только потому, что я решил сгруппировать контролы.
Собственно пару слов о группировке, рассматривая проблему вывода текста я называл один из способов - создание под текст буфера большего размера с отрисовкой только нужной части. Тут можно было пойти аналогичным способом, но немного подумав я решил сделать иначе. Как часто мы рантайм добавляем новые кнопки? Причем не в момент запуска приложения, а именно в процессе работы перестраиваем интерфейс? Да, такое бывает, но в большинстве случаев интерфейс постоянен, в том смысле что конфигурация каждого меню и каждой панели задается еще на этапе загрузки.
Основываясь на этом предположении я решил что в основе должно быть именно статичное меню, это существенно упростило менеджер квадов. Для динамических элементов управления осталась возможность создавать/удалять их рантайм, не привязываясь к "статичным" контролам.
Так или иначе на производительности это особо не сказывается, фактически, при таком подходе, фпс соответствует выводу одного полигона с текстурой во весь экран и ограничивающим фактором тут уже является производительность CPU и рассылка контролам оповещений от клавиатуры и мыши.
Этот же подход может быть с успехом применен при выводе спрайтов, системы частиц, импостеров, травы и многого другого, состоящего из малого числа полигонов.
Так же хочу отметить что осталась еще одна нерешенная проблема, над которой еще предстоит поработать - вывод текста. Да, я помню что я об этом написал целую статью :) Собственно даже на картинке выше вы можете видеть разные надписи, так о чем же речь? Речь, как всегда, об оптимизации. Этот цикл статей был посвящен оптимизации работы с геометрией, а текст предоставляет нам новую проблему, связанную с оптимизацией переключения текстур.
Дело в том, что каждая надпись так или иначе представляет из себя отдельную текстуру (методика описана в статье "Рисуем "Hello World!"), при этом, нам необходимо выполнить минимум два действия: 1) - забиндить новую текстуру текста, 2) - отрисовать геометрию текста с новой текстурой. Таким образом мы не можем выводить текст в одном пакете с геометрией контролов и вынуждены рисовать его отдельно (так сделано в моем модуле uGUI), для каждого биндя свою геометрию и свою текстуру (геометрия всех надписей одной группы хранится в одном отдельном буфере VBO). Какие есть пути опимизации:
Ну и последнее слово - нынче модно делать надписи под углом, искажать их, крутить и прочее (привет Delphi XE2), вся описанная методика (а так же текущая реализация этого в моем модуле) позволяет отрендерить все GUI в отдельную текстуру и извращатсья над ней всеми доступными методами, наложив на любую модельку и применив любимые шейдера.
На этом цикл статей, посвященных GUI и оптимизации вывода low-poly, редко-меняющихся объектов можно считать закрытым. В дальнейших статьях мы разберемся с оптимизацией вывода множества динамических низкполигональных объектов(таких как частицы) и объектов с одинаковой геометрией - инстансов.
С первой статьи цикла уже прошло много времени, потому для начала я напомню о чем шла речь и основные выводы:
Первый раз о проблеме вывода низкополигональных объектов мы заговорили в статье "Рисуем "Hello World!", где было решено что множество однотипных низкополигональных объектов (отдельных букв текста) лучше объединять в один буфер или текстуру, причину этого мы разобрали в статье "Сколько стоит полигон", где на практике убедились в том, что вывод лишнего полигона без растеризации практически ничего не стоит и существенно дешевле лишнего бинда буфера, а если и приходится это делать, то лучше группировать их в пакеты по сотне полигонов. Ну и в последней статье, "Рисуем GUI. Часть первая, немного тестов", мы в очередной раз убедились что архитектура современных видеокарт ужасно плохо стыкуется с низкополигональной графикой, даже в плане задания трансформаций. Так мы выяснили, что при работе с отдельными квадами можно получить стократный прирост производительности, просто отказавшись от конвеера трансформаций OpenGL, заменив его прямой модификацией вершинных данных.
Осталось решить одну маленькую проблему - как при выводе пакета данных исключить отрисовку какой-то части буфера VBO, чтоб иметь возможность воспользоваться всеми описанными преимуществами. Можно легко отрезать начало или конец буфера, но это делу не поможет, если вы попытаетесь нарисовать нечто подобное:
Какие есть способы - их несколько, частично они уже упоминались в предыдущих статьях:
- Один бинд буфера + множественный DrawCall;
- Один бинд буфера + массив указателей на индексы отдельных элементов с использованием команд glMultiDraw*;
- Коллапс полигонов.
Коллапс, она же свертка, или сжатие объекта в материальную точку, он же "вырожденный полигон", он же "полигон с нулевой площадью" - процесс, при котором на выходе мы получим полигон без растеризируемых пикселей. Теперь вспоминаем - не встречалось ли нам это определение раньше? Встречалось, и даже выделено в начале этой статьи. Другими словами, если мы "свернем" полигон, то наша задача превратится в задачу, описанную в статье "Сколько стоит полигон", а из тестов мы уже знаем, что это очень дешевая процедура.
Сама процедура свертки квада очень простая - мы, используя методику описанную в предыдущей статье, просто обновляем координаты квада, указывая одинаковое значение для всех 4-х вершин (к примеру координату (0,0,0)). Свернутый полигон во-первых не будет отображаться (реализация свойства Visible), во-вторых - не требует особых ресурсов, что нам и требовалось.
Подытожим сказанное - для эффективного рендеринга GUI нужно выполнить несколько условий:
- Объединить всю геометрию в один буфер, для исключения переключения между буферами VBO;
- Вместо стандартного конвеера трансформаций OpenGL использовать прямое обновление буфера VBO в видеопамяти, причем выполнять такое обновление только для нужной части буфера и только по требованию;
- Для управления видимости использовать "свертку" полигонов;
- Рисовать всю геометрию одним пакетом.
В приведенном примере выводится примерно 700 элементов управления, что соответствует примерно 4к полигонов. На вывод этого множества потребовалось всего 2 команды рисования, причем только потому, что я решил сгруппировать контролы.
Собственно пару слов о группировке, рассматривая проблему вывода текста я называл один из способов - создание под текст буфера большего размера с отрисовкой только нужной части. Тут можно было пойти аналогичным способом, но немного подумав я решил сделать иначе. Как часто мы рантайм добавляем новые кнопки? Причем не в момент запуска приложения, а именно в процессе работы перестраиваем интерфейс? Да, такое бывает, но в большинстве случаев интерфейс постоянен, в том смысле что конфигурация каждого меню и каждой панели задается еще на этапе загрузки.
Основываясь на этом предположении я решил что в основе должно быть именно статичное меню, это существенно упростило менеджер квадов. Для динамических элементов управления осталась возможность создавать/удалять их рантайм, не привязываясь к "статичным" контролам.
Так или иначе на производительности это особо не сказывается, фактически, при таком подходе, фпс соответствует выводу одного полигона с текстурой во весь экран и ограничивающим фактором тут уже является производительность CPU и рассылка контролам оповещений от клавиатуры и мыши.
Этот же подход может быть с успехом применен при выводе спрайтов, системы частиц, импостеров, травы и многого другого, состоящего из малого числа полигонов.
Так же хочу отметить что осталась еще одна нерешенная проблема, над которой еще предстоит поработать - вывод текста. Да, я помню что я об этом написал целую статью :) Собственно даже на картинке выше вы можете видеть разные надписи, так о чем же речь? Речь, как всегда, об оптимизации. Этот цикл статей был посвящен оптимизации работы с геометрией, а текст предоставляет нам новую проблему, связанную с оптимизацией переключения текстур.
Дело в том, что каждая надпись так или иначе представляет из себя отдельную текстуру (методика описана в статье "Рисуем "Hello World!"), при этом, нам необходимо выполнить минимум два действия: 1) - забиндить новую текстуру текста, 2) - отрисовать геометрию текста с новой текстурой. Таким образом мы не можем выводить текст в одном пакете с геометрией контролов и вынуждены рисовать его отдельно (так сделано в моем модуле uGUI), для каждого биндя свою геометрию и свою текстуру (геометрия всех надписей одной группы хранится в одном отдельном буфере VBO). Какие есть пути опимизации:
- Использовать мультиполигональный вывода текста (каждой букве соответствует свой полигон со своим фрагментом общей текстуры). Плюс такого подхода - текст можно будет рисовать в одном пакете с геометрией контрола и исчезнет необходимость в переключении текстур. Минус - более сложный менеджер текста, который должен учитывать динамическую природу текста, который должен предусмотреть возможность менять шрифты отдельных надписей, увеличение числа полигонов и прочее (подробнее описано в статье "Рисуем "Hello World!")
- Помещать надписи в специальные текстурные атласы. Плюсы в том же, минусы - сложность построения и управления таким атласом, с учетом динамичности текста.
Ну и последнее слово - нынче модно делать надписи под углом, искажать их, крутить и прочее (привет Delphi XE2), вся описанная методика (а так же текущая реализация этого в моем модуле) позволяет отрендерить все GUI в отдельную текстуру и извращатсья над ней всеми доступными методами, наложив на любую модельку и применив любимые шейдера.
На этом цикл статей, посвященных GUI и оптимизации вывода low-poly, редко-меняющихся объектов можно считать закрытым. В дальнейших статьях мы разберемся с оптимизацией вывода множества динамических низкполигональных объектов(таких как частицы) и объектов с одинаковой геометрией - инстансов.
Комментариев нет:
Отправить комментарий