
Оптимизация (подходы).
Если вы попытались поработать с редактором, то уже заметили, что при большом количестве точек, визуализация работает крайне медленно – это и не удивительно.
Подход с перебором массива вершин для каждого слоя, и поочередное их рисование – это не лучшее решения данной задачи.
Поэтому в заключительной части главы мы переработаем функциональную часть нашего графического 2D редактора, отвечающую за визуализацию.
Подход будет следующим :
Во первых мы откажемся от непрерывной перерисовки всех слоев – это крайне неграмотно. На начальных этапах создания приложения – это конечно могло стать основой визуализации, но в дальнейшем необходимо все координально менять.
Сейчас, мы поговорим о том, что такое дисплейные списки OpenGL – так сказать немного теории. Затем мы рассмотрим их работу на реальном примере – а именно:
Каждый слой (кроме текущего активного слоя) при вызове функции визуализации будет вызывать исключительно дисплейный список, изменения в котором будут происходить лишь при внесении изменений в слое.
Сам принцип слоев в этом случае, вместо негативного понижения производительности, который происходил при переборе и визуализации каждого слоя, теперь даст ощутимый прирост: неактивные слои будут исключительно вызывать готовые дисплейные списки, откомпилированные в памяти графического адаптера – их визуализация будет проходить на много быстрее.
Так, например, поместив изображение на нижний слой, а производя рисование на другом слое – мы практически не будем тратить системные ресурсы.
Так же мы оптимизируем сам алгоритм отрисовки при создании дисплейного списка – мы не будем перебором отрисовывать каждую точку изображения, а будем подготавливать массив для быстрой отрисовки.
Итак, начнем с того, что рассмотрим, что же такое дисплейные списки.
Простым примером может служить 3D модель игрового персонажа – единожды построив и отрисовав ее в программе – затем каждый раз выполнять сложный алгоритм отрисовки, если можно сохранить в дисплейном списке и отрисовывать простым вызовом уже частично обработанных и кешированных команд, хранимых в специально отведенной памяти графического редактора. Принцип построения дисплейного списка крайне прост:
В кратце опишем основные положительны моменты использования списков отображения (помимо, само собой, прироста производительности от кеширования в списке самой геометрии):
• В первую очередь – это матричные операции. Рассчитываемые матрицы могут быть сохранены в списке отображения реализацией OpenGL, что является первым, и одним из самых весомых плюсов использования дисплейных списков.
• Текстуры при использовании OpenGL версии 1.1. В остальных случаях лучше использовать текстурные объекты
• Отрисовка битовых карт , а так же изображений, так как ваш формат хранения этих данных вряд ли является тем, который идеален для аппаратуры видеоадаптера, чего как раз нельзя сказать о дисплейном списке.
• Операции с источниками света, материалами, а так же их свойствами в дисплейных списках так же должно обеспечить прирост производительности визуализации OpenGL, так как сама по себе, настройка материала может стать довольно медленной операцией, в связи с дополнительными вычислениями. В случае использования дисплейного списка – вы по сути будите хранить кешированные значения этих вычислений.
Вот такое краткое теоретическое введение.
Теперь давайте перейдем к оптимизации функций визуализации нашего графического 2D редактора.
Первым делом класс anLayer обзоведется новыми функциями для управления созданем и удалением дисплейных списоков.
// функции удаления слоя
public void ClearList()// проверяем факт существования дисплейного списка с номером, хранимым в ListNom}
if ( Gl.glIsList(ListNom) == Gl.GL_TRUE)
{// удаляем его в случае существования}
Gl.glDeleteLists(ListNom,1);
// проверяем факт существования дисплейного списка с номером, хранимым в ListNom}
if ( Gl.glIsList(ListNom) == Gl.GL_TRUE)
{// удаляем его в случае существования}
Gl.glDeleteLists(ListNom,1);
// и генерируем новый номер
ListNom = Gl.glGenLists(1);
// создаем дисплейный список
Gl.glNewList(ListNom, Gl.GL_COMPILE);
// вызывая обычную визуализацию (не из списка)
RenderImage( false );
// хаверщаем создание дисплейного списка
Gl.glEndList();
Как видно из кода, при создании дисплейного списка для данного слоя вызывается отрисовка функции RenderImage с дополнительным параметром false – этот параметр указывает на то, что необходимо полностью провести визуализацию, а не использовать дисплейный список для данного слоя.
Теперь рассмотрим изменения функции public void RenderImage().
Если раньше мы отрисовывали геометрию перебирая все точки в массиве и выводя каждую (выглядело это так)
// устанавливаем заданный в ней цвет int col1 = DrawPlace[ax, bx, 0];
int col2 = DrawPlace[ax, bx, 1];
int col3 = DrawPlace[ax, bx, 2];
Gl.glColor3f(( float )DrawPlace[ax, bx, 0] / 255.0f, ( float )DrawPlace[ax, bx, 1] / 255.0f, ( float )DrawPlace[ax, bx, 2] / 255.0f);
// и выводим ее на экран
Gl.glVertex2i(ax, bx);
То теперь мы будем использовать отрисуовку сразу массивов вершин и цветов целиком. При этом выполняется отрисовка дисплейных списоков или отрисовка напрямую, но с использованием массива вершин.
Это намного экономичнее, чем сообщать при отрисовке о каждой вершине.
Нам нужно только правильно указать формат, в котором представлены данные, и создать сами массивы данных:
Обновленный код, для визуализации слоя (с подробными комментариями)
if (FromList) // указана визуализация из дисплейного списка, следовательно данный слой не активен}
{// вызываем дисплейный список}
Gl.glCallList(ListNom);
else // данный слой активен, и визуализацию необходимо делать на ходу
{// счетчик номеров элементов, которые должны участвовать в визуализации}
int count = 0;
// проходим по всем точкам рисунка
for ( int ax = 0; ax < Width; ax++)
{for ( int bx = 0; bx < Heigth; bx++)
{// если точка в координатах ax,bx не помечена флагом "прозрачная"
if (DrawPlace[ax, bx, 3] != 1)
{// не самый красивый способ, но так мы подсчитаем количество действительно значимых точек слоя,
// которые должны быть визуализированны
count++;}
}
}
// данный массив будет заполнен, а затем передан для быстрой отрисовки геометрии (точек в нашем случае)
// колч. точек * 2 (для хранения координат x и y каждой точки, которая будет отрисована
int [] arr_date_vertex = new int [count * 2];
// данный массив будет содержать значения цветов для всех отрисовываемых точек
// колч. точек * 3 (для хранения координат R G B значений цветов каждой точки, которая будет отрисована
float [] arr_date_colors = new float [count * 3];
// счетчик элементов для создания массивов, которые будут переданы в реализацию OpenGL c
// помощью функции glDrawArrays
int now_element = 0;
// теперь, когда мы выделили массив необходимого размера,
// мы заполним его необходимыми значениями
for ( int ax = 0; ax < Width; ax++)
{for ( int bx = 0; bx < Heigth; bx++)
{// если точка в координатах ax,bx не помечена флагом "прозрачная"
// если данная точка НЕ помечена флагом, сигнализирующим о том, что она не должна быть визуализированна
if (DrawPlace[ax, bx, 3] != 1)
{// заносим координаты точки (ax , bx ) в массив, кторый будет передан для визуализации
arr_date_vertex[now_element * 2] = ax;
arr_date_vertex[now_element * 2 + 1] = bx;
// заносим значения составляющих цвета, сразу перенося их в формат float
arr_date_colors[now_element * 3] = ( float )DrawPlace[ax, bx, 0] / 255.0f;
arr_date_colors[now_element * 3 + 1] = ( float )DrawPlace[ax, bx, 1] / 255.0f;
arr_date_colors[now_element * 3 + 2] = ( float )DrawPlace[ax, bx, 2] / 255.0f;
// подсчет добавленных элементов в массивы
now_element++;}
}
}
// теперь, когда массивы с геометрическими данными и данными о цветах подготовлены
// включаем функцию использования массивов вершин и цветов
Gl.glEnableClientState( Gl.GL_VERTEX_ARRAY);
Gl.glEnableClientState( Gl.GL_COLOR_ARRAY);
// передаем массивы вершин и цветов, указывая количество элементов массива, приходящихся
// на один визуализируемый элемент (в случае точек - 2 координаты: х и у, в случае цветов - 3 составляющие цвета)
Gl.glColorPointer(3, Gl.GL_FLOAT, 0, arr_date_colors);
Gl.glVertexPointer(2, Gl.GL_INT, 0, arr_date_vertex);
// вызываем функцию glDrawArrays которая позволит нам визуализировать наши массивы, передоав их целиком
// а не передавая в цикле каждую точку
Gl.glDrawArrays( Gl.GL_POINTS, 0, count);
// деактивируем режим использования массивов геометрии и цветов
Gl.glDisableClientState( Gl.GL_VERTEX_ARRAY);
Gl.glDisableClientState( Gl.GL_COLOR_ARRAY);
Далее. Перейдем к классу class anEngine
Первым делом изменения в функиции установки активного слоя
// текущий лой больше не будет активным -следовательно надо создать новый дисплейный список для его быстрой визуализации}
((anLayer)Layers[ActiveLayerNom]).CreateNewList(); // новый активный слой, получает установленный активный цвет для предыдущего активного слоя
((anLayer)Layers[nom]).SetColor( ((anLayer)Layers[ActiveLayerNom]).GetColor() );
// установка номера активного слоя
ActiveLayerNom = nom;
Функция SwapImage теперь отрисовывет дисплейные списки, для всех слоев (параметр true в функии image, которые не являются активными). Активный слой отрисовывается напрямую, т.к. он постоянно меняется.
// вызываем функцию визуализации в нашем слое}
for( int ax = 0; ax < Layers.Count; ax++)
{// эсли данный слой является активным в данный момент}
if( ax == ActiveLayerNom)
{// вызываем визуализацию данного слоя напрямую}
((anLayer)Layers[ax]).RenderImage( false );
else
{// вызываем визуализацию слоя из дисплейного списка}
((anLayer)Layers[ax]).RenderImage( true );
Ну вот и все. Полученный редактор не является идеальным, но демонстрирует множество методов для работы с растровыми данными – основные принципы развертывания классов для управления изображениями, отрисовкой, работы с дисплейными списками и наборами вершин и т.д.
В главе 7 мы рассмотримдобавление функций растровых преобразований – наша программа получит несколько графических фильтров для обработки изображений.
Обсуждение данного урока: Создание растрового редактора - часть 5. Оптимизация.
Далее: 7.1 Алгоритмы обработки растровых изображений - теоретическое введение.