6.5 Создание растрового редактора - часть 5. Оптимизация.

Необходимые знания:
Вам могут понадобится следующие статьи:
Если вы попытались поработать с редактором, то уже заметили, что при большом количестве точек визуализация работает крайне медленно, это и не удивительно. Подход с перебором массива вершин для каждого слоя и поочередное их рисование – это не лучшее решения данной задачи.

Поэтому в заключительной части главы мы переработаем функциональную часть нашего графического 2D-редактора, отвечающую за визуализацию.

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

Сейчас мы поговорим о том, что такое дисплейные списки OpenGL, так сказать, немного теории. Затем мы рассмотрим их работу на реальном примере.

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

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

Так, например, поместив изображение на нижний слой, а производя рисование на другом слое, мы практически не будем тратить системные ресурсы. Также мы оптимизируем сам алгоритм отрисовки при создании дисплейного списка: мы не будем перебором отрисовывать каждую точку изображения, а будем подготавливать массив для быстрой отрисовки.

Что же такое дисплейные списки?

Простым примером может служить 3D-модель игрового персонажа: единожды построив и отрисовав ее в программе, зачем каждый раз выполнять сложный алгоритм отрисовки, если можно сохранить ее в дисплейном списке и отрисовывать простым вызовом уже частично обработанных и кешированных команд, хранимых в специально отведенной памяти графического редактора. Принцип построения дисплейного списка крайне прост:
  1. Вы генерируете номер дисплейного списка.
  2. Сохраняете его для дальнейшей работы с этим списком.
  3. Единожды визуализируете необходимую графику в контейнере между вызовами функций glNewList() и glEndList().
  4. Теперь в функции, отвечающей за визуализацию кадра, достаточно лишь вызвать необходимый дисплейный список с помощью команды glCallList.
Основные положительны моменты использования списков отображения (помимо, само собой, прироста производительности от кеширования в списке самой геометрии):

• Во-первых, это матричные операции. Рассчитываемые матрицы могут быть сохранены в списке отображения реализацией OpenGL, что является первым и одним из самых весомых плюсов использования дисплейных списков.

• Текстуры при использовании OpenGL версии 1.1. В остальных случаях лучше использовать текстурные объекты.

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

• Операции с источниками света, материалами и их свойствами в дисплейных списках также должно обеспечить прирост производительности визуализации OpenGL, так как сама по себе настройка материала может стать довольно медленной операцией в связи с дополнительными вычислениями. В случае использования дисплейного списка вы по сути будите хранить кешированные значения этих вычислений.

Вот такое краткое теоретическое введение.

Теперь давайте перейдем к оптимизации функций визуализации нашего графического 2D-редактора. Первым делом класс anLayer обзаведется новыми функциями для управления созданием и удалением дисплейных списков:

/*http://esate.ru, Anvi*/


// функции удаления слоя
public void ClearList()
{
  // проверяем факт существования дисплейного списка с номером, хранимым в ListNom 
  if ( Gl.glIsList(ListNom) == Gl.GL_TRUE)
  {
  // удаляем его в случае существования 
    Gl.glDeleteLists(ListNom,1);
  }
}

public void CreateNewList()
{
  // проверяем факт существования дисплейного списка с номером, хранимым в 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(). Если раньше мы отрисовывали геометрию, перебирая все точки в массиве и выводя каждую, выглядело это так:

/*http://esate.ru, Anvi*/


if (DrawPlace[ax, bx, 3] != 1)
{
  // устанавливаем заданный в ней цвет 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);

}


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

Обновленный код для визуализации слоя (с подробными комментариями):

/*http://esate.ru, Anvi*/


// функция визуализации слоя 
public void RenderImage( bool FromList)
{
  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. Первым делом изменения в функции установки активного слоя:

/*http://esate.ru, Anvi*/


// функция для установки номера активного слоя 
public void SetActiveLayerNom( int nom)
{
  // текущий слой больше не будет активным, следовательно, надо создать новый дисплейный список для его быстрой визуализации 
  ((anLayer)Layers[ActiveLayerNom]).CreateNewList(); // новый активный слой получает установленный активный цвет для предыдущего активного слоя 
  ((anLayer)Layers[nom]).SetColor( ((anLayer)Layers[ActiveLayerNom]).GetColor() );

  // установка номера активного слоя 
  ActiveLayerNom = nom;
}


Функция SwapImage теперь отрисовывет дисплейные списки для всех слоев (параметр true в функции image, который не является активным). Активный слой отрисовывается напрямую, т.к. он постоянно меняется:

/*http://esate.ru, Anvi*/


// визуализация 
public void SwapImage()
{
  // вызываем функцию визуализации в нашем слое 
  for( int ax = 0; ax < Layers.Count; ax++)
  {
    // если данный слой является активным в данный момент,
    if( ax == ActiveLayerNom)
    {
      // вызываем визуализацию данного слоя напрямую 
      ((anLayer)Layers[ax]).RenderImage( false );
    }
    else
    {
      // вызываем визуализацию слоя из дисплейного списка 
      ((anLayer)Layers[ax]).RenderImage( true );
    }
  }
}


Ну вот и все. Полученный редактор не является идеальным, но демонстрирует множество методов для работы с растровыми данными: основные принципы развертывания классов для управления изображениями, отрисовкой, работы с дисплейными списками и наборами вершин и т.д.

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

Прикрепленные файлы для скачивания:

Нет доступа к просмотру комментариев.

^