Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Render large amount of text as char objects  (Read 2864 times)

0 Members and 1 Guest are viewing this topic.

e1ee7

  • Newbie
  • *
  • Posts: 4
    • View Profile
Render large amount of text as char objects
« on: October 01, 2015, 05:28:37 pm »
Hi all.

I'm working on roguelike game, and so my first idea of how to render the map was like this:


void fill(sf::RenderWindow &window, const std::string &map) {
        sf::Text text;
        text.setFont(font_);              // font_ is loaded on app start
        text.setCharacterSize(20);

        sf::FloatRect backgroundRect;
        sf::RectangleShape background;
        background.setFillColor(sf::Color(0x00, 0x00, 0x00)); // Always black for simplicity

        sf::Color wallColor(0x9E, 0x86, 0x64);
        sf::Color floorColor(0x33, 0x33, 0x33);

        for (int y = 0; y < 60; ++y) {
                for (int x = 0; x < 80; ++x) {
                        const char ch = data[y * 80 + x];
                        if (ch == '#') {
                                text.setColor(wallColor);
                        } else {
                                text.setColor(floorColor);
                        }
                        text.setString(ch);
                        text.setPosition(x * 20, y * 20);

                        backgroundRect = text.getLocalBounds();
                        background.setPosition(sf::Vector2f(backgroundRect.width, backgroundRect.height));

                        window.draw(background, text.getTransform());
                        window.draw(text);
                }
        }
}

int main() {
    sf::RenderWindow window;
    window.create(sf::VideoMode(1920, 1200), "test",
                        sf::Style::Close | sf::Style::Titlebar), settings);
    window.setFramerateLimit(30);

    const char map[] = "80 cols x 60 lines of chars"; // 80x60 here, basically "# ...... #"
    while(window.isOpen()) {
        window.clear();
        fill(window, map);
        window.display();
    }
}
 

Well, it's a little bit more complex but idea is shown. If i comment-out map fill then my app takes about 1% CPU, but if not - it takes 100% CPU all the time. So I guess fill function is not efficient enough. Is there a way to speed up things?

The map is just a matrix (an array if you want) of chars. Different chars (as they represents different entities, like walls, doors, etc) have different attributes (background/foreground color).

Thanks in advance.

e1ee7

  • Newbie
  • *
  • Posts: 4
    • View Profile
Re: Render large amount of text as char objects
« Reply #1 on: October 01, 2015, 05:38:12 pm »
Well, I know that using VertexArray is possible solution, but I have no idea how can I prepare a texture for it. I mean it's really easy to draw a character on a screen, but I don't get how to transform character (e.g.sf::Text and sf::Font with back/fore ground colors) into a piece of texture.

mkalex777

  • Full Member
  • ***
  • Posts: 206
    • View Profile
Re: Render large amount of text as char objects
« Reply #2 on: October 01, 2015, 06:20:51 pm »
I just solved similar issues with text.
The best way is to create text instance for single time, before game loop.
At initialization time, assign font and CharacterSize.
At render time do not touch CharacterSize and do not create new Text instance.
Profiler shows that these operations consumes too much time.

I implemented it and now I have ~500 fps with about 500 game objects with text (name of object and short info). Also my game loop contains console renderer which draws for about 100-200 colored texts per frame and it consumes for about 5fps from 500...

But I do not rendering text letter by letter. I just split it on lines and then split each line on single color parts. So, I call window.draw for a single time per each color part in the text line.

Update: also, remove SetFrameLimit from your code, it just slow down the game. If you want constant and smooth frame rate, use SetVerticalSyncEnabled(true) instead of SetFrameLimit
And second update - remove background fill from the loop. Just render rectangle for entire area for a single time before loop
« Last Edit: October 01, 2015, 06:34:52 pm by mkalex777 »

e1ee7

  • Newbie
  • *
  • Posts: 4
    • View Profile
Re: Render large amount of text as char objects
« Reply #3 on: October 01, 2015, 07:07:30 pm »
Thanks for your reply. Unfortunately that doesn't work for me. I create two tiles (for floor and wall):

struct tile {
    sf::Text text;
    sf::RectangleShape background;
};

int main() {
        // Wall
        std::shared_ptr<tile> wall(new tile);
        wall->text.setFont(font);
        wall->text.setCharacterSize(20);
        wall->text.setString('#');
        wall->text.setColor(wallColor);

        sf::FloatRect backgroundRect = wall->text.getLocalBounds();
        wall->background.setFillColor(sf::Color(0x00, 0x00, 0x00));
        wall->background.setPosition(sf::Vector2f(backgroundRect.width, backgroundRect.height));

        // Floor
        std::shared_ptr<tile> floor(new tile);
        floor->text.setFont(font);
        floor->text.setCharacterSize(20);
        floor->text.setString('.');
        floor->text.setColor(floorColor);

        backgroundRect = floor->text.getLocalBounds();
        floor->background.setFillColor(sf::Color(0x00, 0x00, 0x00));
        floor->background.setPosition(sf::Vector2f(backgroundRect.width, backgroundRect.height));
}

 

and then fill std::vector with these shared_ptr's, so I have a vector with 80x60 (4800) pointers. Now in a main loop I do:

window.clear();
for (int y = 0; y < 60; ++y) {
        for (int x = 0; x < 80; ++x) {
                const std::shared_ptr<tile> t = data[y * 80 + x];
                t->text.setPosition(x * 20, y * 20);
                draw(t->background, t->text.getTransform());
                draw(t->text);
        }
}
window.display();
 

and CPU load is still near 100% (90% actually).

P.S. I'm studying libtcod trying to implement some parts of this library in SFML (not a port, just taking the ideas from this library). It uses SDL1.2/SDL2 as a rendering library. I found an example of drawing text maps (with true colors background/foreground) and it's rocket fast. I can't believe I can't do similar things using SFML.
« Last Edit: October 01, 2015, 07:16:49 pm by e1ee7 »

mkalex777

  • Full Member
  • ***
  • Posts: 206
    • View Profile
Re: Render large amount of text as char objects
« Reply #4 on: October 01, 2015, 07:16:53 pm »
Thanks for your reply. Unfortunately that doesn't work for me. I create two tiles (for floor and wall):

struct tile {
    sf::Text text;
    sf::RectangleShape background;
};

int main() {
        // Wall
        std::shared_ptr<tile> wall(new tile);
        wall->text.setFont(font);
        wall->text.setCharacterSize(20);
        wall->text.setString('#');
        wall->text.setColor(wallColor);

        sf::FloatRect backgroundRect = wall->text.getLocalBounds();
        wall->background.setFillColor(sf::Color(0x00, 0x00, 0x00));
        wall->background.setPosition(sf::Vector2f(backgroundRect.width, backgroundRect.height));

        // Floor
        std::shared_ptr<tile> floor(new tile);
        floor->text.setFont(font);
        floor->text.setCharacterSize(20);
        floor->text.setString('.');
        floor->text.setColor(floorColor);

        backgroundRect = floor->text.getLocalBounds();
        floor->background.setFillColor(sf::Color(0x00, 0x00, 0x00));
        floor->background.setPosition(sf::Vector2f(backgroundRect.width, backgroundRect.height));
}

 

and then fill std::vector with these shared_ptr's, so I have a vector with 80x60 (4800) pointers. Now in a main loop I do:

window.clear();
for (int y = 0; y < 60; ++y) {
        for (int x = 0; x < 80; ++x) {
                const std::shared_ptr<tile> t = data[y * 80 + x];
                t->text.setPosition(x * 20, y * 20);
                draw(t->background, t->text.getTransform());
                draw(t->text);
        }
}
window.display();
 

and CPU load is still near 100% (90% actually).

Then you can scan your vector for a single color sequences, and then draw all sequence by two calls - first for background and the second for text. It should improve performance.
Because with current code you're calling window.draw at least 4800*2=9600 times per frame. It's very much.

Update: I didn't notice right away that you are using separate instances for each letter. It's overhead and will consume a lot of memory. Just create single text and single rectangle and use it to render any tile cell.

Also, you can improve performance by remove GetLocalBounds call. This call consumes valuable time. By design you are using fixed size cells, so you can calculate position with no need for this call. I'm doing it where possible and it gives a little better performance
« Last Edit: October 01, 2015, 07:37:19 pm by mkalex777 »

dabbertorres

  • Hero Member
  • *****
  • Posts: 505
    • View Profile
    • website/blog
Re: Render large amount of text as char objects
« Reply #5 on: October 01, 2015, 09:48:38 pm »
First, you're storing pointers to tiles in an array, that means the CPU has to chase down all those pointers to do what you want, that's going to take a non-trivial (in this case due to the amount) amount of time.
Rather than doing a std::vector of std::shared_ptr, just store all of those tiles directly in the std::vector. That will help some.

Two, sf::Text does not directly support different color characters. However, you can amend this. You are right about being able to use sf::VertexArray. sf::Font has two functions of interest in this case. sf::Font::getTexture and sf::Font::getGlyph.

Read the documentation for those functions, make sure you read the documentation for sf::VertexArray, and you should be able to put the two together I think. :)
(This also could, if done correctly, give you the benefit of using only one text object for your whole map, rather than one text object per tile.)
(Note: Making your own Text class of sorts will simplify usage a lot.)