Any Bored Code Optimizers Out There?


authoreyes

Member
Joined
Oct 2, 2007
Messages
161
Website
www.markandmarina.com
Hey all,

Was fixin' to get back to coding my pandora game, and I figured I'd throw the rendering portion to the mercy of the dev community.

See, I am only a C++/game programmer hobbyist (career is more java stuff), so I figured I'd see if anyone with some time could point out any inefficiencies here. Basically, this is doing a foreground/background 16x16 tile mapped scrolling engine. It runs fine, but a bit slow. I am sure there is room to optimize, so maybe someone has a few tips...Worst comes to worst, I can render a smaller area and scale up (but loose the high res look it has now)...

I know it may be hard to follow some of this as my commenting is lacking, so if something seems stupid, feel free to shout...

EDIT: added some optimizations.

Code:
class RenderUtils {

    public :

        //TODO: this needs to be pulled out into a main cycle loop
        //class since artificial intelligence and such is going in here now
        static void render(SDL_Surface *screen, GameState *state) {
            clear_screen(screen);

            pre_process_events(state);
            GameLifeManager::move_baddies(state);
            procedural_render(screen, state);
            post_process_events(state);

            SDL_Flip(screen);
        }

        static void procedural_render(SDL_Surface *screen, GameState *state) {

            //render_sprite_vector(screen, state, state->parallax_objs);
            render_backgrounds(screen, state);
            render_sprite_vector(screen, state, state->screen_stats);
            //TODO: attachedsprites needs to be below and check z depth
            render_sprite_vector(screen, state, state->player.attached_sprites);
            render_sprite(screen, state, state->player);
            render_sprite_vector(screen, state, state->sprites);
            render_sprite_vector(screen, state, state->baddies);
        }

        static void render_backgrounds(SDL_Surface *screen, GameState *state) {

            unsigned int x = (int)state->camera_x / LEVEL_TILE_WIDTH;
            unsigned int x_mod_tilewidth = (((int)state->camera_x) % LEVEL_TILE_WIDTH);

            render_parallax(screen, state, x, x_mod_tilewidth, true);
            render_parallax(screen, state, x, x_mod_tilewidth, false);
        }

        static void render_parallax(SDL_Surface *screen, GameState *state, unsigned int x, unsigned int x_mod_tilewidth, bool background) {

            DecoratedSurface *parallax_surface = (background == true ? &(state->background_surface) : &(state->foreground_surface));
            std::vector<std::vector<Tile*> > *grid = (background == true ? &(state->back_grid) : &(state->fore_grid));

            if (background) {
                calculate_background_parallax_offsets(state, &x, &x_mod_tilewidth, background);
            }

            unsigned int total_distance = x + (GAME_WINDOW_WIDTH / LEVEL_TILE_WIDTH) + 1;
            unsigned int scrolled_tile_width = LEVEL_TILE_WIDTH;

            //screen_x represents the absolute position of the start of the display tiles,
            //so we always repaint starting at 0,0
            unsigned int screen_x = 0;

            SDL_Rect tile_pos;

            tile_pos.y = 0;
            tile_pos.h = LEVEL_TILE_HEIGHT;

            //first x pass will be a shaved tile (possibly < 16 pixels on the left of screen)
            if ((int)state->camera_x >> 4 != 0) {
                scrolled_tile_width = LEVEL_TILE_WIDTH - x_mod_tilewidth;
                render_screen_y_tiles(screen, parallax_surface, grid, state, tile_pos, screen_x, x, scrolled_tile_width, x_mod_tilewidth, background);
                screen_x += scrolled_tile_width;
                x++;
            }

            for (x;x<total_distance; ++x) {
                scrolled_tile_width = LEVEL_TILE_WIDTH;

                render_screen_y_tiles(screen, parallax_surface, grid, state, tile_pos, screen_x, x, scrolled_tile_width, 0, background);
                screen_x += scrolled_tile_width;
           }
        }

        static void render_screen_y_tiles(SDL_Surface *screen, DecoratedSurface *parallax_surface, std::vector<std::vector<Tile*> > *grid, GameState *state,
            SDL_Rect tile_pos, unsigned int screen_x, unsigned int x, unsigned int scrolled_tile_width, unsigned int x_mod_tilewidth, bool background) {

            std::vector<Tile*> y_vector = (*grid)[x];

            for (unsigned int y=0;y<y_vector.size();++y) {
                unsigned int tile_id = y_vector[y]->id;

                unsigned int transparent_tile = (background ? state->transparent_tile_f : state->transparent_tile_B);

                if (tile_id != transparent_tile) {

                    //calc tile rip from tile strip
                    tile_pos.x = (tile_id << 4) + x_mod_tilewidth;

                    tile_pos.w = scrolled_tile_width;

                    unsigned int screen_y = (y << 4);

                    blit_tile(screen, state, parallax_surface, &tile_pos, screen_x, screen_y, false);
                }
            }
        }

        static void calculate_background_parallax_offsets(GameState *state, unsigned int *x, unsigned *x_mod, bool background) {
            if (state->background_scroll_offset == 0) {
                state->background_parallax_camera_x = ++state->background_parallax_camera_x;
                //TODO: this should not use the default, but the specific level impl
                state->background_scroll_offset = GameState::default_background_scroll_offset;
            }
            *x = state->background_parallax_camera_x / LEVEL_TILE_WIDTH;
            *x_mod = (((int)state->background_parallax_camera_x) % LEVEL_TILE_WIDTH);
        }

        static void render_sprite_vector(SDL_Surface *screen, GameState *state, std::vector<Sprite*> sprites) {
            for (std::vector<Sprite*>::iterator it = sprites.begin();it != sprites.end(); ++it) {
                render_sprite(screen, state, *(*it));
            }
        }

        static void render_sprite_vector(SDL_Surface *screen, GameState *state, std::vector<Sprite> sprites) {
             for (std::vector<Sprite>::iterator it = sprites.begin();it != sprites.end(); ++it) {
                render_sprite(screen, state, (*it));
            }
        }

        static void render_sprite(SDL_Surface *screen, GameState *state, Sprite sprite) {
            SDL_Rect tile_pos;
            if (sprite.descriptor->num_frames == 1) {
                tile_pos.x = tile_pos.y = 0;
                tile_pos.w = sprite.descriptor->width;
                tile_pos.h = sprite.descriptor->height;
            } else {
                tile_pos.x = sprite.get_current_frame() * sprite.descriptor->width;
                tile_pos.y = 0;
            }

            tile_pos.w = sprite.descriptor->width;
            tile_pos.h = sprite.descriptor->height;
            blit_tile(screen, state, sprite.descriptor->sprite_sheet, &tile_pos, sprite.x, sprite.y, sprite.heading == W);
        }

        static void blit_tile(SDL_Surface *screen, GameState *state, DecoratedSurface *surface, SDL_Rect *tile_pos, unsigned int x, unsigned int y, bool flip) {
           SDL_Rect offset;
           offset.x = x;
           offset.y = y;

           SDL_BlitSurface(surface->surface, tile_pos, screen, &offset);
        }

        static void pre_process_events(GameState *state) {
            for (std::vector<TriggeredEvent*>::iterator it = state->level->pre_process_triggered_events.begin();it != state->level->pre_process_triggered_events.end(); ++it) {
                if ((*it)->status == EXECUTING) {
                    (*it)->next_cycle(state);
                }
            }
        }

        static void post_process_events(GameState *state) {
            for (std::vector<TriggeredEvent*>::iterator it = state->level->post_process_triggered_events.begin();it != state->level->post_process_triggered_events.end(); ++it) {
                if ((*it)->status == EXECUTING) {
                    (*it)->next_cycle(state);
                }
            }
        }

        static void clear_screen(SDL_Surface *screen) {
            SDL_FillRect(screen, &screen->clip_rect, SDL_MapRGB(screen->format, 0, 0, 0));
        }

};

#endif
 
There seems to be a lot of dynamic/on-the-fly stuff happening (allocating temporary surfaces in "procedural_render", doing color conversion in "blit_tile") inside the main loop.
This should be pulled out of there into the loading/initialization part of the code, because it probably only has to be done only once, not once every frame...

Code:
static void free_surface(SDL_Surface *s) {
    SDL_FreeSurface(s);
    s = NULL;
}
Your Java influence becomes obvious here :) This is C++, so "s" only contains a copy of the pointer and the NULL-ing is pointless :)
 
hmn said:
There seems to be a lot of dynamic/on-the-fly stuff happening (allocating temporary surfaces in "procedural_render", doing color conversion in "blit_tile") inside the main loop.
This should be pulled out of there into the loading/initialization part of the code, because it probably only has to be done only once, not once every frame...

Code:
static void free_surface(SDL_Surface *s) {
    SDL_FreeSurface(s);
    s = NULL;
}
Your Java influence becomes obvious here :) This is C++, so "s" only contains a copy of the pointer and the NULL-ing is pointless :)

All excellent points! Thanks - I will make those changes (although I have to look closer at the color conversion/how often that needs to occur) - damn that should have been pretty obvious to me....
 
Last edited by a moderator:
I've not looked it too much over, but there does seem to be lots of allocating temp stuff, which maybe costing you; why not allocate lists once before rendering ever, and then re-use lists when you can, or pre-group items or something?

I didn't look to see if theres any dynamic rotations going on, or the like; you can pre-flip and pre-rotate and other jazz to save on dynamic stuff.

Are you rendering everythign every frame? If the background has not scrolled, you might be able to get away with rendering background bits over sprites, and then rendering sprites in their new location (and being carefuil not to do this for sprites that have not moved or state changed); ie: "dirty rendering" can be awesome, but in scrolling background games oyu tend to lose all that benefit. IF you're using hardware blitting anywhere, you'll be fine, but in pure software ... are you using alpha-sprites, in software rendering, which kilsl you? perhaps you can alpha some and not others?

Sorry, just got 30 seconds and no time to read the code but a coupel things lept to mind :)

jeff

Are you making surfaces in your render loop? blit_tile crteating surfaces, rather than just blitting? anythign thats creating or somethign that can be pre-done or pre-calculated should be, avoid all the new/destroy/surfacy stuff you can :)
 
@skeezix

Yeah, it does do some allocation in the render loop for temp surfaces as sprites as well as tiles filter through the same code....Since the sprites can be arbitrary dimensions, I re-allocate in the render, but you are right, that's costly, so I'll pre-create those before rendering....Also, I try to not draw tiles that are alpha only, and right now I do render every loop as I figured I'd add dirty rendering as a later optimization....I wanted to get it all tuned before going that route...

Thanks so much for the great advice!

EDIT:

Actually - I remember now (been like 5 months since I looked at this) - right when I started chemo I ripped out the opengl code - looks like there is tons I can cut down on here now that was built around that)
 
Tile engines are great fun :) Which device is the target? 800x480 on pandora is a lot of pixels, so a multilayer tileset will hurt without hw blitting :) But on stuff like gp2x era 320x240 you shoudl be pretty optimized .. in brute force C you could render 40fps with a couple layers and a few dozen sprites.

For the very-back tile layer, make sure no alpha or other blending is going on, can save you a buttload since often your backmost layer is the full-screen layer; for additional static layers on top you can flag if a tile is 'completely transparent' (ie: nothing there), versus partially transparent, versus full no transparent needed. ie: a pure background layer shoudl be fast but full coverage; then another layer may be stuff like mountains or somethign on top which is mostly full tiles with no alpha, plus a lot of no tile at all (100% transparent), with mayeb some highlgityhs (furtniture or somethign ewhich is transparent). Then feature-layer like blood spatters or highlights and artwork, which is almost nothign on the layer at all, but each bit is full trans tile. Then sprites on top. And maybe another layer on top, for sutff like overhangings or whatever.

Just be sure to not be 'rendering' all the blanks, and not doing trenasparent renders on stuff with no trans, etc.

That always helped me, when doing software render loops..

jeff
 
skeezix said:
Got any screenshots? Curious now :)

jeff

Jeff -

Nothing new :)

Same old video from before...my crappy placeholder gfx...

Video

EDIT:

Sorry - the pandy is the target device - I started coded it a few months before chemo...
 
Last edited by a moderator:
Hi authoreyes,

From your vimeo website:

A quick demo of a work in progress engine I am developing in C++/sdl/opengl for the Pandora handheld[...]

Don't forget to use notaz's improved SDL :)

Bye, take care, and keep up the good work !

Magic Sam
 
Last edited by a moderator:
One potential optimisation - it depends on exactly what code the compiler is emitting - is that each time you go around the for loops for STL vectors, you're calling the size function. If the vector size is not changing during your loop you can just grab the size once. This is only an optimisation if the size grabbing code does some form of indirect lookup, virtual function call, or isn't inlined.

So for example:
vector<int>::iterator i;
for(i = vec.begin(); i != vec.end(); ++i)
becomes
vector<int>::iterator i, e;
for(i = vec.begin(), e=vec.end(); i != e; ++i)

Also you're using post increments in your for loops. This can be bad juju - again, depending on the compiler/optimisation level/etc. Using post or pre increments in this situation doesn't change the functionality, *BUT* it can make it harder for the compiler to optimise the loops. So use pre-increment unless necessary.
 
hdonk said:
Also you're using post increments in your for loops. This can be bad juju - again, depending on the compiler/optimisation level/etc. Using post or pre increments in this situation doesn't change the functionality, *BUT* it can make it harder for the compiler to optimise the loops. So use pre-increment unless necessary.

Gotcha....

I'll update the code with those tips...thanks much...

I am going to look into how much stuff I can trim out of vectors and such, but some of that (the eventing pipelines) can change per render....However, I could code in a "dirty" concept there as well...

EDIT:

Yup, noticed other inefficiencies as well...so used to my java mindset, where I sacrifice optimization for code re-use as clusters with oodles of memory perform with sloppy code...I should do a bit of overloading instead of trying to waste cycles getting things to match signatures...work to be done...
 
Last edited by a moderator:
Back
Top