Graphics

VUEngine now supports three kinds of visual elements that can display something on the Virtual Boy’s screens: Sprites, Wireframes and Printing.

Sprites are the means by which the engine displays 2D images, while Wireframes are used to display non textured 3D geometry shapes.

They are components that can be attached to an Entity. But they can be instantiated on demand and controlled directly without the need to attach them to any Entity. Both are created by the ComponentManager class.

# Sprites

Sprites work as a sort of logical window that peaks into graphical memory, either into BGMAP space or directly into CHAR space, and draws what is seen through that window by means of a WORLD or an OBJECT or group of OBJECTs.

CHAR, BGMAP, OBJECT and WORLD are all hardware concepts of the Virtual Boy’s architecture. The engine provides classes that correspond to each one of these for their management.

A CharSet represents one or more CHARs and treats them as a single unit. These hold the indexes of color data that underlies the graphics that the Virtual Boy’s Visual Image Processor (VIP) displays. Each CHAR is an 8×8 pixel matrix, or tile, from which complex images can be composed. CharSets are constructed by providing a CharSetSpec that specifies its properties

Take the following image as an example from which a CharSetSpec will be defined:

The above image’s size is 32×48 pixels, which translates to 4×6 tiles. The Spec that defines it is the following:

CharSetROMSpec ActorCharsetSpec =
{
    // Number of chars in function of the number of frames to load at the same time
    4 * 6,

    // Whether it is shared or not
    true,

    // Whether the tiles are optimized or not
    false,

    // Tiles array
    ActorTiles,

    // Frame offsets array
    NULL,
};

CharSets can have a unique usage or they can be shared to display more than one copy of the same image without increasing the memory footprint in the CHAR memory space. In the above example, the ActorTiles is a reference to the array that actually holds the pixel data.

CHAR memory is arranged as a unidimensional array. Visually, it would look like this:

In such arrangement, the CHARs or tiles of this CharSet cannot be directly drawn to reconstruct the original image. For that, it is necessary to define a specific bidimensional arrangement of the CHARs relative to each order. Array maps that are interpreted as 2D matrices, where each point references a tile in the CharSet, are used to provide such bidimensional order. In the engine these are encapsulated in a Texture class.

Textures need their own TextureSpec to be instantiated and properly initialized. The following corresponds to the Spec for the texture that reconstruct the original image:

TextureROMSpec ActorTextureSpec =
{
    (CharSetSpec*)&ActorCharsetSpec,

    // Pointer to the map array that defines how to use the tiles from the char set
    ActorMap,

    // Horizontal size in tiles of the texture (max. 64)
    4,

    // Vertical size in tiles of the texture (max. 64)
    6,

    // Padding added to the size for affine/hbias transformations (cols, rows)
    {1, 1},

    // Number of frames that the texture supports
    1,

    // Palette index to use by the graphical data (0 - 3)
    0,

    // Flag to recyble the texture with a different map
    false,

    // Flag to vertically flip the image
    false,

    // Flag to horizontally flip the image
    false,
};

A Texture’s map has to be loaded in BGMAP memory when it is displayed by a BgmapSprite. But graphical memory allocation isn’t required when the graphical data is displayed using a ObjectSprite since it only requires the map array to reference from OBJECT memory the CHARs from the CharSet in the right bidimensional order.

Finally, Textures are displayed by Sprites, either from BGMAP memory through a single WORLD, or by rendering each CHAR into OBJECT memory. The following SpriteSpec exemplifies the former in reference to the TextureSpec above:

BgmapSpriteROMSpec ActorSpriteSpec =
{
    // Sprite
    {
        // VisualComponent
        {
            // Component
            {
                // Allocator
                __TYPE(BgmapSprite),

                // Component type
                kSpriteComponent
            },

            // Array of function animations
            NULL
        },

        // Spec for the texture to display
        (TextureSpec*)&ActorTextureSpec,

        // Transparency mode (__TRANSPARENCY_NONE, __TRANSPARENCY_EVEN or __TRANSPARENCY_ODD)
        __TRANSPARENCY_NONE,

        // Displacement added to the sprite's position
        {0, 0, 2, 0},
    },

    // The display mode (__WORLD_BGMAP, __WORLD_AFFINE, __WORLD_OBJECT or __WORLD_HBIAS)
    __WORLD_BGMAP,

    // Pointer to affine/hbias manipulation function
    NULL,

    // Flag to indicate in which display to show the texture (__WORLD_ON, __WORLD_LON or __WORLD_RON)
    __WORLD_ON,
};

This shows how Specs are chained together for derived classes by having at the top the Spec of the base class and adding new fields, relevant to the derived class, to its Spec. In this case, the BgmapSpriteSpec adds the last 3 attributes to the SpriteSpec.

With these Specs defined, the original image can be displayed by instantiating a Sprite and positioning it appropriately:

extern SpriteSpec ActorSpriteSpec;

Sprite sprite = Sprite::safeCast(ComponentManager::createComponent(NULL, (ComponentSpec*)&ActorSpriteSpec));

if(!isDeleted(sprite))
{
    PixelVector spritePosition =
    {
        __SCREEN_WIDTH / 2, __SCREEN_HEIGHT / 2, 0, 0
    };

    Sprite::setPosition(sprite, &spritePosition);
}

CharSets and Textures are reusable, which means that multiple Textures can share the same CharSet and that more than one Sprite can display the same Texture. The intricacies of how these relationships are worked out by the engine depend on the allocation type of the CharSet, which in turn depends on animations.

# BGMAP Sprites

The Sprite class cannot be directly instantiated since it is abstract. The first instantiable Sprite is the BgmapSprite. These use a whole VIP’s WORLD to display a region of BGMAP memory.

These kind of Sprites support 3 display modes in function of the underlying hardware’s capabilities:

  • BGMAP
  • AFFINE
  • H-BIAS

# Affine transformations

Affine transformation effects are supported both in hardware and by VUEngine. The effect to apply to a given Sprite is determined by the corresponding pointer in the BgmapSpriteSpec. If NULL, the engine applies a default full transformation by calling Affine::transform; otherwise, the provided affine function is applied.

An affine function must have the following signature:

static int16 BgmapSprite::someAffineEffect(BgmapSprite bgmapSprite);

The engine reserves a region of the param tables space for any BgmapSprite initialized with a BgmapSpriteSpec that sets its display mode as __WORLD_AFFINE. The manipulation of that space is done through the param attribute. Refer to the official Virtual Boy’s documentation for the meaning of the param table for affine effects.

# H-Bias effects

These kind of effects apply a horizontal displacement on a per-line basis to a Texture that lives in BGMAP memory. This allows to achieve horizontal waving effects.

static int16 BgmapSprite::someHBiasEffect(BgmapSprite bgmapSprite);

The engine reserves a region of the param tables space for any BgmapSprite initialized with a BgmapSpriteSpec that sets its display mode as __WORLD_HBIAS. The manipulation of that space is done through the param attribute. Refer to the official Virtual Boy’s documentation for the meaning of the param table for h-bias effects.

# Frame blending

It is possible to go beyond the Virtual Boy’s 4 color palette, black plus 3 shades of red, with a technique called “frame blending” (also often referred to as “HiColor”). If one pixel shows one color in one game frame, but another in the next, through the effects of persistence of vision, the pixel’s color actually appears to be in-between those two frame blended colors.

The appearance of frame blended colors is very stable on an original Virtual Boy, especially if used sparingly. The larger an area of a frame blended color becomes, the more it flickers. Note that on emulators, due to the Virtual Boy’s refresh rate of 50Hz usually not matching the refresh rate of the screen the emulator image is being viewed on, there’s a much heavier flickering visible on frame blended colors.

Factoring in all possible combinations, with this technique it would theoretically be possible to achieve up to 10 different colors. However, mixing adjacent colors, e.g. medium red and bright red, yields the most pleasant to the eye, as in most stable, least flickering, results, while combinations of colors with larger distances, e.g. black and bright red, so flicker more and also do not differ much from those mixes of adjacent colors, not adding substantially to the palette of available colors. Thus, we have settled to work with a palette of 7 colors, containing only combinations of adjacent colors.

VUEngine includes a special Sprite, FrameBlendBgmapSprite, that supports frame blending out of the box. It takes a Texture that is basically a vertical spritesheet containing two frames of the same image that differ only in pixels that should be blended. Each game frame, the FrameBlendBgmapSprite toggles between displaying the upper or the lower half of the Texture to the user.

This way, a higher visual fidelity can be achieved on a per-Sprite basis, at the cost of higher CHAR and BGMAP memory requirements.

The following shows an example of a frame blending Texture and how the resulting 7 color image would appear to the player.

# OBJECT Sprites

The ObjectSprite uses OBJECTs to render CHARs in one of the 4 posible WORLDS in OBJECT display mode. As all the Sprites, they use a Texture, but its map is used directly by the ObjectSprite to configure the OBJECTs. They are more flexible than BgmapSprite, but use more memory and are heavier to process, both by the CPU and the VIP.

# Wireframes

The other kind of visual component are Wireframes. These are non solid 3D shapes that draw 1 pixel wide lines or circles. They leverage the ability of writing directly to the video frame buffers after the VIP has finished its drawing procedures during the current game frame. Accessing video memory with the CPU is slow and even more so when reading back from it, which is unavoidable when drawing single pixels.

Example of a Wireframe Mesh

Wireframe creation and configuration is done with a WireframeSpec, which look like the following:

MeshROMSpec ActorMeshSpec =
{
    {
        // Component
        {
            // Allocator
            __TYPE(Mesh),

            // Component type
            kWireframeComponent
        },

        {0, 0, 0},

        // Wireframe's lines' color
        __COLOR_BLACK,

        // Transparency mode (__TRANSPARENCY_NONE, __TRANSPARENCY_EVEN or __TRANSPARENCY_ODD)
        __TRANSPARENCY_NONE,

        // Flag to render the wireframe in interlaced mode
        false,
    },

    // Segments that compose the mesh
    (PixelVector(*)[2])ActorMeshSegments
};

The engine provides a few kinds of Wireframes: Mesh, Line, Sphere and Asterisk. Depending on the specific class, its corresponding Spec will add specific attributes. In the case of a MeshSpec, it requires an array of segments:

const PixelVector ActorMeshSegments[][2]=
{
    // base
    { {-64, 64, -64, 0}, { 64, 64, -64, 0} },
    { {-64, 64,  64, 0}, { 64, 64,  64, 0} },
    { {-64, 64, -64, 0}, {-64, 64,  64, 0} },
    { { 64, 64, -64, 0}, { 64, 64,  64, 0} },

    // vertex
    { {0, -64, 0, 0}, {-64, 64, -64, 0} },
    { {0, -64, 0, 0}, {-64, 64,  64, 0} },
    { {0, -64, 0, 0}, { 64, 64, -64, 0} },
    { {0, -64, 0, 0}, { 64, 64,  64, 0} },

    // limiter
    { {0, 0, 0, 0}, {0, 0, 0, 0} },
};

Then, as it was the case with Sprites, a Wireframe can be instantiated by calling the corresponding manager:

extern WireframeSpec ActorMeshSpec;

Wireframe wireframe = Wireframe::safeCast(ComponentManager::createComponent(NULL, (ComponentSpec*)&ActorMeshSpec));

if(!isDeleted(wireframe))
{
    Vector3D wireframePosition =
    {
        -__PIXELS_TO_METERS(__SCREEN_WIDTH / 2), __PIXELS_TO_METERS(__SCREEN_HEIGHT / 2), 0, 0
    };

    Wireframe::setPosition(wireframe, &wireframePosition);
}

# Printing

VUEngine uses a special Sprite to provide a printing facility, both for UI and gaming purposes, as for helping debugging. The following are the available methods to print different primitive data types:

    /// Print a string.
    /// @param string: String to print
    /// @param x: Column to start printing at
    /// @param y: Row to start printing at
    /// @param font: Name of font to use for printing
    void text(const char* string, int32 x, int32 y, const char* font);

    /// Print an integer value.
    /// @param value: Integer to print
    /// @param x: Column to start printing at
    /// @param y: Row to start printing at
    /// @param font: Name of font to use for printing
    void int32(int32 value, uint8 x, uint8 y, const char* font);

    /// Print a hex value.
    /// @param value: Hex value to print
    /// @param x: Column to start printing at
    /// @param y: Row to start printing at
    /// @param length: Digits to print
    /// @param font: Name of font to use for printing
    void hex(WORD value, uint8 x, uint8 y, uint8 length, const char* font);

    /// Print a float value.
    /// @param value: Float value to print
    /// @param x: Column to start printing at
    /// @param y: Row to start printing at
    /// @param precision: How many decimals to print
    /// @param font: Name of font to use for printing
    void float(float value, uint8 x, uint8 y, int32 precision, const char* font);

Printing is used as follows:

Printer::text
(
    "Hello World",
    0,
    0,
    "Default"
);

To erase all printing, use:

Printer::clear();

VUEngine comes with a default font for writing to the printing Layer, but you can replace it with any number of custom fonts.

# Animations

Only Sprites support animations. There are basically 2 ways to allocate the graphical data for animations in the system’s video memory:

  • To load all the CHARs for all the frames of animation at once
  • To load only the CHARs that correspond to a single frame of animation at any given time

The first approach puts stress on video memory since depending on the size of each frame and the number of animation frames, it can quickly deplete CHAR memory. The second alternative puts the stress on the CPU since it has to rewrite the pixel data when the frame of animation changes. Using one or the other depends on the memory and performance requirements of the game.

CharSets can be shared by multiple Textures. Whether this is the case or not, is determined by the shared flag of the CharSetSpec:

CharSetROMSpec ActorCharsetSpec =
{
    // Number of chars in function of the number of frames to load at the same time
    4 * 6,

    // Whether it is shared or not
    true,

    // Whether the tiles are optimized or not
    false,

    // Tiles array
    ActorTiles,

    // Frame offsets array
    NULL,
};

When requesting a CharSet by providing a shared CharSetSpec, the engine will only allocate a CharSet once, and any subsequent request will be served with the previously created instance. This saves both work and graphics memory, as well as CPU performance.

The overshoot of a shared CharSetSpec that only allocates a single frame at any give moment is that any Sprite that uses a Texture which reference that CharSet will show a change of animation if any of them changes the frame making all instances to be in sync:

Since it would be overkill to play animations on all Sprites underlyed by a shared CharSet, the engine runs the animations only on the first Sprite.

On th other hand, when using a non-shared CharSetSpec to create a CharSet, each request will be served with a new CharSet instance. This permits to have different sprites with the same graphics but displaying different frames of animation:

To load the complete pixel data of all the animation frames of an animation, the CharSetSpec must specify the total amount of CHARs used by all of the:

CharSetROMSpec ActorMultiframeCharsetSpec =
{
    // Number of chars in function of the number of frames to load at the same time
    4 * 6 * 12,

    // Whether it is shared or not
    true,

    // Whether the tiles are optimized or not
    false,

    // Tiles array
    ActorTiles,

    // Frame offsets array
    NULL,
};

Allocating all frames of animation has a meaning in regards to Textures too. Textures define how to organize the CHARs or tiles of a CharSet into a bidimensional plane. This order can be applied directly when displaying the image using OBJECTs through instances of ObjectSprite. But when using BGMAPs with BgmapSprite, the Texture’s map has to be allocated in BGMAP memory to be displayed by means of a WORLD. In this case, there is an analogous difference between allocating all the frames of the animation at once or only one.

To load all the maps for all the animation frames of an animation in BGMAP memory, the TextureSpec must specify the total number of frames:

TextureROMSpec ActorMultiframeTextureSpec =
{
    (CharSetSpec*)&ActorMultiframeCharsetSpec,

    // Pointer to the map array that defines how to use the tiles from the char set
    Map,

    // Horizontal size in tiles of the texture (max. 64)
    4,

    // Vertical size in tiles of the texture (max. 64)
    6,

    // Padding added to the size for affine/hbias transformations (cols, rows)
    {0, 0},

    // Number of frames that the texture supports
    12,

    // Palette index to use by the graphical data (0 - 3)
    0,

    // Flag to recyble the texture with a different map
    false,

    // Flag to vertically flip the image
    false,

    // Flag to horizontally flip the image
    false,
};

In this scenario, each Sprite that uses the same Texture can display a different frame of the animation.