This site uses cookies to ensure you get the best experience. More info
Got it!

Switching to SFML.Net



What is SFML.Net?

Let’s start by clearing up any unknowns.
Firsly, what exactly is SFML.Net? Or SFML itself?

SFML.Net is the official binding of SFML (Simple and Fast Multimedia Library) for .NET languages.
SFML offers cross-platform compatibility and easy to use object oriented API.

While providing an API for manipulating graphics and audio, it also offers a networking module and cross-platform compatible threads implementation.

As you can see, SFML will provide me with a wide range of different tools besides rendering, which might prove to be useful in the future.

Still, I didn’t really know much about SFML and it’s API up to this point. That’s why, to start things up and learn how to work with it, I decided to integrate it directly to my terminal emulation library - Malison, as one of the renderers, along with a simple example application.

Integrating SFML into Malison

Firstly, in order to use the SFML.Net, I had to add the Nuget package for it into the new Malison.SFML project, which adds all the necessary .dll files into the project’s references.

Colors

Because SFML provides it’s own Color class, I had to add an extension method to the built in TermColor structure, so we can easily create the SFML counterpart.

public static class TermColorExtensions
{
    public static Color ToSFMLColor(this TermColor color)
    {
        return new Color(color.R, color.G, color.B, color.A);
    }
}

Now, having color compliance implemented, let’s move on to implementing the GlyphSheet class.

GlyphSheet

The GlyphSheet is a wrapper for font’s spritesheet with built-in cache for performance. It will be used to easily render characters in given encoding based on given spritesheet.

Now, the Windows Forms counterpart was implemented with Bitmap instance as given spritesheet, and a cache consisting of smaller Bitmaps that represent the characters.

In case of SFML, we will be working with different entities. Firstly, SFML introduces textures.

A texture is more or less an image that lives in the GPU’s memory and is used for drawing things on the screen.

So, we will be creating a new texture based on the spritesheet that is representing our desired font. This texture will be then passed to the GlyphSheet’s constructor.

Now, instead of a cache for smaller bitmaps, we can limit the memory usage and use another kind of entities from SFML library - sprites.

Sprites are renderable objects which represent some part of given texture. A rendered sprite can be equivalent to the texture’s image or it can be just a small fragment of it.

We will still be caching the sprite objects to not repeat the creation process for same character multiple times, but we will use much less memory as the original spritesheet is loaded only once.

public class GlyphSheet
{
    private Dictionary<Character, Sprite> spriteCache;

    public Texture SpriteSheet { get; private set; }

    public int GlyphsPerRow { get; private set; }
    public int GlyphsRows { get; private set; }

    public int Width { get { return (int)SpriteSheet.Size.X / GlyphsPerRow; } }
    public int Height { get { return (int)SpriteSheet.Size.Y / GlyphsRows; } }

    public GlyphSheet(Texture spritesheet, int perRow, int rows)
    {
        SpriteSheet = spritesheet;
        GlyphsPerRow = perRow;
        GlyphsRows = rows;

        spriteCache = new Dictionary<Character, Sprite>();
    }

    public void Draw(RenderWindow window, int x, int y, Character character)
    {
        // Do not render empty characters
        if (character.Code == 0)
            return;

        Sprite sprite = GetSprite(character);
        sprite.Position = new Vector2f(x * Width, y * Height);

        // Render background
        RectangleShape rs = new RectangleShape(new Vector2f(Width, Height));
        rs.Position = new Vector2f(x * Width, y * Height);
        rs.FillColor = character.BackColor.ToSFMLColor();
        window.Draw(rs);

        // Render character
        window.Draw(sprite);
    }

    private Sprite GetSprite(Character character)
    {
        Sprite sprite = null;

        // If sprite was cached, return it
        if(spriteCache.TryGetValue(character, out sprite))
        {
            return sprite;
        }

        int column = character.Code % GlyphsPerRow;
        int row = character.Code / GlyphsPerRow;

        IntRect srcRect = new IntRect(column * Width, row * Height, Width, Height);

        sprite = new Sprite(SpriteSheet);
        sprite.TextureRect = srcRect;
        sprite.Color = character.ForeColor.ToSFMLColor();

        // Cache it
        spriteCache[character] = sprite;

        return sprite;
    }
}

Now, the Draw method accepts another SFML entity - the RenderWindow class instance. This instance is the representation of the application window and it is used to draw things on screen, access event handlers and so on.

As the project is building without errors, let’s move on to an example which will use SFML.Net to render terminal contents on screen.

An example

The example I will cover, will be a simple class that represents the application and implements a very basic and simple game loop.
The rendering part will consist of the same example of features highlight as the Windows Forms example app so the code for this will be omitted for simplicity.

Let’s start by implementing a constructor for the SFMLApp class.

public SFMLApp()
{
    Window = new RenderWindow(new VideoMode(1280u, 768u), "Malison SFML Example");

    Window.SetVisible(true);
    Window.SetVerticalSyncEnabled(true);
    Window.Closed += Window_Closed;
    Window.SetFramerateLimit(60);

    Texture fontTexture = new Texture(@"Resources\cp437_16x16.png");

    GlyphSheet = new GlyphSheet(fontTexture, 16, 16);

    Terminal = new Terminal(1280 / 16, 768 / 16, Encoding.GetEncoding(437));
}

To start of we will need to create a new RenderWindow instance to create a window for our application. To the constructor we pass our desired window size and window’s name.

After we do that, we can configure it, add any necessary callbacks or even set the desired framerate limit.

Additionally, we are loading the font’s spritesheet into a new texture.
Based on it, we create a new GlyphSheet which has 16 rows and 16 columns.

Finally, we create a new Terminal instance, which exactly as much rows and columns to fill the whole window. The Terminal will be encoding characters in 437 encoding to support the CP437 font.

Having everything initialized, let’s add a Run() method, that will contain the basic game loop.

public void Run()
{
    while(Window.IsOpen())
    {
        // Dispatch events to work with native event loop
        Window.DispatchEvents();

        Update();
        Render();
    }
}

The loop is pretty straightforward and as you can see, SFML also provides an easy way to handle native events (such as window resizing and so on).

The Update() method, is where we handle any terminal changes, but as I said earlier, I will omit it because it’s just a highlight of Malison’s features, which you can see if you want on the Malison’s repository.

The Render() method clears the window’s contents, iterates through the terminal’s cells and renders each of them on screen by using the created GlyphSheet instance and finally calls Window.Display() to render all batched draw calls.

private void Render()
{
    Window.Clear();

    for (int i = 0; i < Terminal.Size.X; i++)
    {
        for (int j = 0; j < Terminal.Size.Y; j++)
        {
            GlyphSheet.Draw(Window, i, j, Terminal.Get(i, j));
        }
    }

    Window.Display();
}

And now what is left is to implement the void Main() in our Program.cs file.

class Program
{
    [STAThread]
    static void Main()
    {
        SFMLApp app = new SFMLApp();

        app.Run();
    }
}

And now as we did that, we can take a look at the results:

Example of working SFML.Net application Example of working SFML.Net application

Wrapping up

Today I have explained what is SFML.Net and what it can do.

As SFML will be much more performant, I will be able to finally implement more features into Praedium and focus on that.

Also, I will have to make some changes to Praedium’s engine due to the difference of API, but having integrated it into Malison I probably won’t have much trouble with that.

As you can see, SFML has quite a low level but very straightforward API. I have definitely not covered most of it’s features so be sure to check them out!

Also, I will finally need to document the Malison library as the project’s README is very bare-boned.

That’s all for today’s post! If you have any questions or feedback, feel free to drop a comment below.

See you next time!