2015-06-04 4 views
1

В настоящее время я ищу узкие места в своем коде, и, оказывается, GUI является одним из них. Ну, на самом деле не GUI, а динамический текст, который там нарисован.FreeType OpenGL dynamic Text = abysmal performance

Initialization

 if (FT_Init_FreeType(&m_FreeType)) 
      throw Helpers::ExceptionWithMsg("Could not init freetype lib"); 

     if (FT_New_Face(m_FreeType, "res\\fonts\\FreeSans.ttf", 0, &m_FontFace)) 
      throw Helpers::ExceptionWithMsg("Could not open font"); 

     m_ShaderID = ... // Loads the corresponding shader 
     m_TextColorLocation = glGetUniformLocation(m_ShaderID, "color"); 
     m_CoordinatesLocation = glGetAttribLocation(m_ShaderID, "coord"); 

     glGenBuffers(1, &m_VBO); 

     FT_Set_Pixel_Sizes(m_FontFace, 0, m_FontSize); 
     glyph = m_FontFace->glyph; 

     glGenTextures(1, &m_Texture); 

     glActiveTexture(GL_TEXTURE0); 
     glBindTexture(GL_TEXTURE_2D, m_Texture); 

     // We require 1 byte alignment when uploading texture data 
     glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 

     // Linear filtering usually looks best for text 
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 

     // Clamping to edges is important to prevent artifacts when scaling 
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 

     glUseProgram(m_ShaderID); 
      glUniform4f(m_TextColorLocation, m_TextColor.x, m_TextColor.y, m_TextColor.z, m_TextColor.w); 
     glUseProgram(0); 

Что я делаю: я инициализировать FreeType, получить шрифт, инициализировать шейдер и все формы.

Затем я создаю vbo для текстурыCoordinates, устанавливаю пиксели для шрифта, получаю глиф.

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

Rendering:

glUseProgram(m_ShaderID); 

    glEnable(GL_BLEND); 
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 

    glActiveTexture(GL_TEXTURE0); 
    glBindTexture(GL_TEXTURE_2D, m_Texture); 

    // Linear filtering usually looks best for text 
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 

    // Set up the VBO for our vertex data 
    glEnableVertexAttribArray(m_CoordinatesLocation); 
    glBindBuffer(GL_ARRAY_BUFFER, m_VBO); 
    glVertexAttribPointer(m_CoordinatesLocation, 4, GL_FLOAT, GL_FALSE, 0, 0); 

    GLfloat cursorPosX = m_X; 
    GLfloat cursorPosY = m_Y; 
    for (size_t i = 0; i < m_Text.size(); ++i) 
    { 
     // If Loading a char fails, just continue 
     if (FT_Load_Char(m_FontFace, m_Text[i], FT_LOAD_RENDER)) 
      continue; 

     glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, glyph->bitmap.width, glyph->bitmap.rows, 0, GL_ALPHA, GL_UNSIGNED_BYTE, glyph->bitmap.buffer); 

     // Calculate the vertex and texture coordinates 
     GLfloat x2 = cursorPosX + glyph->bitmap_left * m_SX; 
     GLfloat y2 = -cursorPosY - glyph->bitmap_top * m_SY; 
     GLfloat w = glyph->bitmap.width * m_SX; 
     GLfloat h = glyph->bitmap.rows * m_SY; 

     PointStruct box[4] = 
     { 
      { x2, -y2, 0, 0 }, 
      { x2 + w, -y2, 1, 0 }, 
      { x2, -y2 - h, 0, 1 }, 
      { x2 + w, -y2 - h, 1, 1 } 
     }; 

     // Draw the character on the screen 
     glBufferData(GL_ARRAY_BUFFER, sizeof box, box, GL_DYNAMIC_DRAW); 
     glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); 

     // Advance the cursor to the start of the next character 
     cursorPosX += glyph->advance.x/64 * m_SX; 
     cursorPosY += glyph->advance.y/64 * m_SY; 
    } 

    glDisableVertexAttribArray(m_CoordinatesLocation); 
    glDeleteTextures(1, &m_Texture); 
    glDisable(GL_BLEND); 
    glUseProgram(0); 

Установка шейдера и прочее очевидна.

Для каждого вызова рендеринга я активирую текстуру, привязываю ее, активирую VBO. Я храню свою текстуру. В этом случае я перебираю каждый символ в тексте, загружая его с помощью FT_LOAD_CHAR. Затем я указываю текстуру с glTexImage2D, вычисляю координаты вершин и текстур и рисую все.

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

Я хотел установить параметры текста только один раз в init -> все символы - это поля.

Я хотел установить GL_DYNAMIC_DRAW для GL_STATIC_DRAW ... не так много. Что еще я могу сделать?

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

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

Что действительно беспокоит меня

Одна вещь, которую я действительно не понимаю (может быть солнечный день ...)

Если я не установлю линейной фильтрации в методе визуализации,() Я получаю странные кубики-глифы, но почему? OpenGL - это конечный автомат, параметры текстуры устанавливаются на привязку к текущей. Итак, если я устанавливаю фильтр Min и Mag в GL_LINEAR в инициализации, почему этого недостаточно?

Если я удаляю эти 2 строки в рендере, я получаю лучшую производительность по запросу (гораздо более низкие номера), но он не нарисовал ничего читаемого.

+0

О вашем втором вопросе (глифы?), Ваша текстура, вероятно, не завершена. Поиск «полнота текстуры opengl». –

ответ

4

Это абсолютно будет медленным.

Для каждого рендеринга позвонить мне активировать текстуру, привязать его, включите VBO я храню textureCoordinates. Тогда я перебирать каждый символ в тексте загрузить его с FT_LOAD_CHAR. Затем я указываю текстуру с glTexImage2D, вычисляю координаты вершин и текстур и рисую все.

Проблема, к сожалению, трудно. Вот метод я использую:

  • Существует одна текстура, с форматом GL_RED8, который хранит глифы.

  • Всякий раз, когда требуется новый глиф, он добавляется к текстуре. Это делается путем вызова FT_Render_Glyph() и копирования результата в буфер текстуры. Если новый глиф не подходит, вся текстура глифа изменяется и переупаковывается. (Я использую алгоритм skyline для упаковки глифов, так как это просто.)

  • Если какие-либо новые глифы были добавлены, я звоню glTexSubImage2D(). Код должен быть структурирован так, чтобы он вызывался только один раз за кадр.

  • Для визуализации текста я создаю VBO, который содержит координаты вершин и текстур для всех квадратов, необходимых для визуализации фрагмента текста. (Пожалуйста, поймите, что «quad» означает два треугольника, а не GL_QUAD).

Итак, когда вы изменить то, что текст, который вы хотите сделать,

  • Вы должны обновить VBO, но только один раз за кадр

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

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

Дополнительные соображения - разрывы строк, замещение глифов, кернинг, bidi, общие проблемы с международным текстом, как указать стиль и т. Д. Я рекомендую использовать HarfBuzz в сочетании с FreeType. HarfBuzz обрабатывает сложные проблемы замещения и позиционирования глифов. Ничего из этого не требуется, если ваша программа имеет только текст на английском языке.

Есть некоторые библиотеки, которые делают все это, но я не использовал их.

Альтернативный метод, если вы хотите отрезать гордиев узел, заключается в том, чтобы встроить в ваш браузер веб-браузер, такой как Chromium (Awesomium, WebKit, Gecko-many)), и обработать весь текст.

+0

Другой подход, вместо изменения размера текстуры глифа, когда он заполняется, заключается в том, чтобы выделить новый. Это потребует управления, с помощью которого VBOs ссылаются на текстуру глифа, но могут быть гораздо более эффективными с точки зрения памяти, особенно если ваша платформа требует текстуры POT (что делают некоторые платформы GLES). – MuertoExcobito

+0

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

1

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

// Calculate the vertex and texture coordinates 
    GLfloat x2 = cursorPosX + glyph->bitmap_left * m_SX; 
    GLfloat y2 = -cursorPosY - glyph->bitmap_top * m_SY; 
    GLfloat w = glyph->bitmap.width * m_SX; 
    GLfloat h = glyph->bitmap.rows * m_SY; 

    PointStruct box[4] = 
    { 
     { x2, -y2, 0, 0 }, 
     { x2 + w, -y2, 1, 0 }, 
     { x2, -y2 - h, 0, 1 }, 
     { x2 + w, -y2 - h, 1, 1 } 
    }; 

    // Draw the character on the screen 
    glBufferData(GL_ARRAY_BUFFER, sizeof box, box, GL_DYNAMIC_DRAW); 
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); 

с кодом, который предварительно обрабатывает текст, производит больше PointStruct буфера (не забудьте настроить координаты текстуры для текстуры look-up) и рисует несколько символов на призыв на призыв.

1

Есть два простых варианта улучшения производительности.

  1. Создайте единую текстуру, в которой есть все символы, которые вам нужно отобразить при установленных смещениях. Затем ваша текстовая строка становится моделью (объект-буфер), которая будет ссылаться на правильную серию смещений для создания вашей текстовой строки. Это имеет тот недостаток, что вы не сможете сделать кернинг или любые другие причудливые шрифты.
  2. Используйте Pango и Cairo, чтобы полностью отобразить текст как одно растровое изображение. У этого растрового изображения будет текст, отформатированный с правильным кернированием и присоединением. Затем вы загружаете и рисуете только одну текстуру для всего текста.