Skip to content
CXO2 edited this page Dec 16, 2024 · 20 revisions

Overview

The Genode Graphics module is an extension of the SFML Graphics module. It works by introducing additional abstraction layers or re-implementing some SFML Graphics components. In addition, it introduces several new components, such as Gx::Animation and Gx::SpriteBatch.

Drawable Re-implementation

Most of the drawable classes in SFML have been re-implemented in Genode. They behave and have functions similar to those of their SFML counterparts. However, renderables in the Genode counterpart implement the Gx::Node class, allowing you to integrate with the SceneGraph environment seamlessly.

In addition, some entities provide extra functions than the original SFML entities. Note that you can always use SFML entities as they are, but you will need extra effort to develop additional layers to interact and work with the high-level abstraction classes (such as SceneGraph components). Alternatively, you can mix the low-level interfaces from the Entities module to work with higher-level abstraction classes when dealing with SFML drawable entities. Refer to the SceneGraph documentation for more details.

That being said, here are some notable changes:

Sprite

Gx::Sprite has the Gx::BlendMode property, which can conveniently control the sprite render blending with one of the built-in blending modes.

Text & Font

Font

  • Character width is adjustable; it is still inferred from Character height when left unspecified (which behaves exactly the same as SFML font behavior).

Text

  • Spacing, such as Letter Spacing and Line Spacing, is defined in absolute units rather than as a factor.
  • Italic Shear is configurable.
  • You can set individual character colors based on the character index.
  • Outline offset is adjustable.
  • String can be masked and unmasked.
  • Introduce the OnGeometryUpdated virtual function, which will be called when the geometry is updated.

Render Surface concept

Gx::RenderSurface is one of the building block classes in the graphics module that aims to extend or re-purpose sf::RenderTarget capabilities without re-implementing it or making intrusive changes directly in SFML implementation. This is particularly useful when you need capabilities to aggregate and manipulate vertices or render states from multiple renderable objects while acting similarly to sf::RenderTarget.

By the time this documentation was written, implementing sf::RenderTarget could not have achieved this because the draw function was not virtual and was not overridable. Furthermore, most of the sf::RenderTarget internal states directly deal with the OpenGL render pipeline, which higher-level implementations may find undesirable.

All rendering pipelines in Genode use Gx::RenderSurface instead of sf::RenderTarget as defined in the Gx::Renderable interface. When you find yourself in a situation where you only have a sf::RenderTarget instance but need to deal with one of the Genode rendering pipelines, use the Gx::RenderSurfaceAdaptor class to wrap the sf::RenderTarget object as a Gx::RenderSurface object.

Refer to the Gx::SpriteBatch class for the Gx::RenderSurface implementation.

Animation

Gx::Animation is an animated sprite that utilizes the texture coordinates (also known as texture rect) and transformation properties to animate the sprite. This means you must group your texture into a single sprite sheet/tileset to utilize this class. Here's what the constructor looks like:

Animation(const sf::Texture& texture, const sf::Time& duration, std::initializer_list<Frame> frames)

Parameters:

  • texture: The sf::Texture for the animated sprite.
  • duration: The time that animation will take to finish the animation from start to finish, expressed in sf::Time.
  • frames: The animation frames to use; each frame has properties such as texcoord, origin, position, rotation, and scale that will be applied when the frame is activated.

Important

The duration of each frame currently cannot be fine-controlled. Unfortunately, this is designed by default. The original game has to deal with pre-existing assets where each frame has a fixed duration. The assets then occasionally use extra duplicate frames to make certain frames appear longer.

Refactoring these assets required non-trivial efforts back then; thus, this behavior is preserved. However, this design may be changed in the future.

Caution

Similar to how you would use sf::Texture with sf::Sprite, you must correctly manage the lifetime of your textures and make sure that they live as long as they are used by any resources such as animation and sprite. See The white square problem for more details.

Using multiple textures for a single animation instance is not supported.

This class has a few states to indicate the status of the animation that can be retrieved by calling GetState():

  • Initial: Animation is in the initial state, indicating the instance has been created or the animation has been reset.
  • Running: Animation is currently running.
  • Stopped: Animation is interrupted and no longer running; the last activated frame remains active.
  • Completed: Animation is completed; the last activated frame remains active.

The following code illustrates the usage of Gx::Animation

sf::Texture texture = // Create or load the texture..

// Instantiate animation
auto animation = Gx::Animation
(
    texture,        // Texture to use
    sf::seconds(1), // The animation will last 1 seconds
    {               // Define 5 frames, each frame has 10x10px size and arranged sequentially in horizontal layout
        Gx::Animation::Frame
        {
            {{0, 0}, {10, 10}}
        },
        Gx::Animation::Frame
        {
            {{10, 0}, {10, 10}}
        },
        Gx::Animation::Frame
        {
            {{20, 0}, {10, 10}}
        },
        Gx::Animation::Frame
        {
            {{30, 0}, {10, 10}}
        },
        Gx::Animation::Frame
        {
            {{40, 0}, {10, 10}}
        }
    }
);

// Animation frame can be added after instantiation, but it cannot be removed
animation.AddFrame(Gx::Animation::Frame{ { {50, 0}, {10, 10} } }); // add the 6th frame

// You can also inspect the animation frames
for (std::size_t i = 0; i < animation.GetFrameCount(); i++)
{
    auto& frame = animation.GetFrame(i);
    // Frame is a non-const reference, so you can do something with the frame here..
}

// Alter animation speed properties
animation.SetDuration(sf::seconds(3.f)); // Change animation duration to 3s
animation.SetSpeed(2);                   // Set speed modifer to 2 
                                         // This means the animation will play at twice the speed, and the actual animation duration is halved

// Set how many times the animation will be repeated. The default value is 0 (no repeat)
animation.SetRepeatCount(10); // repeat 10 times

// .. or set the loop mode. When the loop mode is activated, it will be looped indefinitely, and the repeat count will be ignored
animation.SetLoop(true);

// Set animation callback. It will called when the animation state is changed
animation.SetAnimationCallback([] (Gx::Animation& it)
{
    if (it.GetState() == Gx::Animation::AnimationState::Playing)
        std::cout << "Animation started!" << std::endl;
});

To start the animation, simply call the Update(delta) with the delta frame in your game loop

// Update the animation with a delta frame
// You don't have to check the animation state before updating it 
// The animation will only play when the animation state is `Running`
animation.Update(delta);

// Then draw/render the animation either with `sf::RenderTarget` or `Gx::RenderSurface`
surface.Render(animation);

Important

The animation frame has optional transformation properties that will be applied when the frame is activated. If you wish to transform your animation without interfering with the animation frame transform changes, you should apply the transform changes before rendering the animation via transform of the sf::RenderStates or Gx::RenderStates.

Alternatively, you can attach the animation to another Gx::Node object and use that parent to apply the transformation.
See SceneGraph documentation for more details.

Call Stop() and Reset() to stop and restart your animation, respectively. Calling one of these methods will trigger the animation callback.

// Stop the animation. The last activated frame remains active
animation.Stop();

// Reset animation to initial state. The first frame will be activated
animation.Reset();

Sprite Batch

Gx::SpriteBatch provides functionalities to render multiple renderable objects in a single OpenGL render call. This could improve performance significantly when you need to render hundreds or even thousands of objects that share the same texture without manually dealing with vertex arrays.

The implementation is an altered version of @Marioalexsan SFML Sprite Batch implementation that incorporates the Render Surface concept.

The sprite batch class implements the render surface class, so you can use it to render multiple renderable objects like usual.

// Create sprite batch with layer sort mode
Gx::SpriteBatch batch(Gx::SpriteBatch::BatchMode::LayerSort)

Gx::Sprite sprite1 = //...
Gx::Sprite sprite2 = //...
Gx::Animation animation1 = //...
Gx::Animation animation2 = //...

// Then, in the main loop between the main surface/target clear() and display()..
batch.Render(sprite1);
batch.Render(sprite2);

// Render call can be arranged with states.Layer
// For example, the following code will make the batcher to render the animation1 after animation2
auto states = Gx::RenderStates::Default; // Create a new states based on the default states
states.Layer = 1;
batch.Render(animation1, states);

// Switch back to the default layer
states.Layer = 0;
batch.Render(animation2, states);

// ...

// Finally, you can render the batch to an actual surface to render the objects that has been rendered to this batch in a single call
surface.Render(batch);

Important

The Sprite Batch is currently re-computing the vertices of every render call, which could be very inefficient and, on certain occasions, may negate the benefit of having a single render call. This design needs to be improved. Please consider raising a PR if you're interested in making a fix!

Note

Sprite Batch support dealing with multiple renderable objects with different textures. However, using a single texture will yield the best performance. The best practice is to keep the number of textures as low as possible in any scenario.

Limitations

Gx::SpriteBatch has limitations compared to a normal render surface typically made to directly work with sf::RenderTarget.

  • It cannot render sf::VertexBuffer since they're already live in GPU.
  • It doesn't have a view.
  • Render states such as blending mode and shader must be consistent across render calls between objects rendered to the batch.

Clone this wiki locally