Beginner tutorial

Abstract

In this small tutorial we'll first write a program which displays an image on the screen. We'll then modify this program to move the image on the screen using the arrow keys on your keyboard or the mouse.

Initializing sge subsystems

We want to draw an image on the screen, so we need at least two things: An image loader and a renderer. We also need to initialize the sge "core".

Normally, you would have to get a renderer and an image loader plugin, load them and create the according renderers and image loaders from those plugins, but there's a shortcut which we'll take here since it's probably your first glance at sge.

To initialize sge we create a sge::systems object, which will handle the dirty details of initialization for us. That's a one liner

sge::systems sys;

You have to include <sge/systems.hpp> for that. Now we can start initializing, first the core and the image loader:

sys.init<sge::init::core>();
sys.init<sge::init::image_loader>();

The header belonging to init is <sge/init.hpp>.

Then, for the renderer, we need to choose a window resolution and pass it to init :

sys.init<sge::init::renderer>(sge::renderer::screen_size_t(640,480));

And that's it. If you start the program now you probably see a 640x480 window popping up for a split second. Our first sge program! Just for convenience, here's the whole program:

#include <sge/systems.hpp>
#include <sge/init.hpp>

int main()
{
    sge::systems sys;
    sys.init<sge::init::core>();
    sys.init<sge::init::image_loader>();
    sys.init<sge::init::renderer>(sge::renderer::screen_size_t(640,480));
}

Now that we've initialized the subsystems, we can access them via sys.renderer and sys.image_loader.

Drawing an image

The next goal is to draw an image on our newly created window which we can move around later on. In computer graphics, a two dimensional "object" is called a sprite. To draw one or more sprites in sge you have to create a sge::sprite::system, which gets the renderer in its constructor.

sge::sprite::system ss(sys.renderer);

Files to include: <sge/sprite/system.hpp>

Now for the tricky part, loading the image and attaching it to a sge::sprite. We'll be using the image called tux.png which is located in the image directory under the documentation directory. Just copy it into the directory of your sample application. First, we use the image loader to load the image:

sge::image::object_ptr image = sys.image_loader->load_image(SGE_TEXT("tux.png"));

Files to include: <sge/text.hpp>

There are a few new things here, first of all sys.image_loader is a smart pointer, so we cannot use the . (dot) operator to access the load_image function. The same applies to renderer and any other subsystem class.

And what about this strange SGE_TEXT thingy around the string? Well, if you want to use character literals in sge, they have to be narrow literals and you have to embrace them with SGE_TEXT; all this macro does is convert the literal to sge::string's internal format (which is wide or narrow, depending on the FOOBAR macro which you can specify when you compile sge).

The rest of the code should be pretty clear, the load_image function returns an sge::image::object_ptr (again a smart pointer) which we assign to the variable image.

Okay, now we've got an image, but a sprite needs a texture which is stored in the fast VRAM of your graphics card instead of the system RAM. Creating textures is the renderer's job:

sge::renderer::texture_ptr image_texture = 
    sys.renderer->create_texture(
        image->view(),
        sge::renderer::linear_filter,
        sge::renderer::resource_flags::readable);

Files to include: <sge/renderer/texture_filter.hpp>, <sge/renderer/image_view_impl.hpp>

This version of create_texture takes a renderer::const_image_view. Luckily, our image object has a view function which returns just this. The second argument is the filter which is applied to the texture when it's rendered. Linear filter actually blurs the texture a bit, but it's the filter you'll most often want to use for sprites. An alternative would be sge::renderer::point_filter, which does no filtering at all, so with enlarged low resolution images, pixels can be seen. The last argument just specifies that we're not interested in changing this texture dynamically.

Anyway, in theory, we would now be able to use the texture to paste it onto a sprite. If you look at the constructor of sge::sprite::object you'll see that it doesn't take a texture_ptr, but a part_ptr. That's because you don't have to create a separate texture for each sprite, but you can also create a big texture containing smaller sub textures and assign those sub textures to the sprites. This is called "atlasing" and can really boost performance since changing the current texture on the videocard is considered expensive.

Since we're not going to use atlasing in the tutorial, we create a part which is just a wrapper for a single texture. Watch!

sge::sprite::object my_object(
    sge::sprite::point(0,0),
    sge::texture::part_ptr(new sge::texture::part_raw(image_texture)),
    sge::sprite::texture_dim);

Files to include: <sge/texture/part_raw.hpp>, <sge/sprite/object.hpp>

This piece of code should be pretty self explanatory now. Note that my_object is not a smart pointer but a "real" object. We put the sprite on the top left corner of the screen and give it our test texture. For the size parameter, we specify the constant texture_dim which tells sge to choose the texture's dimensions for the sprite's size.

main loop

Having created all the stuff we need, the program still closes immediately after starting it. So let's add a main loop which keeps it going!

while (true)
{
    sge::window::dispatch();
    sge::renderer::scoped_block block_(sys.renderer);
    ss.render(my_object);
}

Files to include: <sge/renderer/scoped_block.hpp>.

Inside the endless loop (we'll fix that when we introduce the input system), sge::window::dispatch is called. This is just a maintenance function so the window properties are updated. You can try removing this line and see what happens if you like.

If you want to render something on the screen, you have to call renderer::begin_rendering and renderer::end_rendering. Yet, since we write our code exception safe (!), we use the renderer::scoped_block class which calls these functions in its constructor and destructor.

Finally, we tell the sprite system to render our sprite.

Here's the complete program:

#include <sge/systems.hpp>
#include <sge/init.hpp>
#include <sge/sprite/system.hpp>
#include <sge/sprite/object.hpp>
#include <sge/renderer/scoped_block.hpp>
#include <sge/renderer/texture_filter.hpp>
#include <sge/renderer/image_view_impl.hpp>
#include <sge/texture/part_raw.hpp>
#include <sge/exception.hpp>
#include <sge/iostream.hpp>
#include <sge/text.hpp>
#include <exception>
#include <iostream>

int main()
try
{
    sge::systems sys;
    sys.init<sge::init::core>();
    sys.init<sge::init::image_loader>();
    sys.init<sge::init::renderer>(sge::renderer::screen_size_t(640,480));

    sge::sprite::system ss(sys.renderer);
    sge::image::object_ptr image = sys.image_loader->load_image(SGE_TEXT("tux.png"));
    sge::renderer::texture_ptr image_texture = 
        sys.renderer->create_texture(
            image->view(),
            sge::renderer::linear_filter,
            sge::renderer::resource_flags::readable);
    sge::sprite::object my_object(
            sge::sprite::point(0,0),
            sge::texture::part_ptr(new sge::texture::part_raw(image_texture)),
            sge::sprite::texture_dim);

    while (true)
    {
            sge::window::dispatch();
            sge::renderer::scoped_block block_(sys.renderer);
            ss.render(my_object);
    }
} 
catch (sge::exception const &e)
{
    sge::cerr << SGE_TEXT("caught sge exception: ") << e.what() << SGE_TEXT("\n");
}
catch (std::exception const &e)
{
    std::cerr << "caught std exception: " << e.what() << "\n";
}

I added code to catch any sge exceptions (the most common one in this piece of code would be could not find image 'tux.png') and also standard exceptions, just to make sure. In case you wonder, yes, it's legal to write try directly after the declaration line of a function.

Note that there are sge iostream equivalents (called sge::cerr, sge::cout and sge::clog) so you don't have to worry if you should use std::cout or std::wcout.

When you start the program now you'll see tux on the top left corner of the screen. To close the program...well, do something radical, it won't close that easily. But we're working on that in part 2 of the tutorial, where we add input support.


Generated on Wed Sep 10 22:17:49 2008 for sge by  doxygen 1.5.5