Draw and publish
Put a picture on the Super Hi-Res screen in a small part of a frame. Use a banked canvas for the interface and a sprite scene for moving objects. Publish only the areas that change.
Choose a method
| Method | Use it for | Cost |
|---|---|---|
A 320x200 canvas surface and ti_gfx_present of changed rectangles | Title screens, HUDs, panels and other areas that change rarely or in small boxes | 32,000 banked bytes. Each publication copies its rows from the bank to the screen. |
| A sprite scene | Many moving sprites on a plain background | Near memory for the list and a strip of 8 lines. No banked memory. |
ti_gfx_present_rows from a near buffer | A small area that the game composes | The near buffer |
You can use these methods together. STARFALL keeps its interface and its pause and game-over panels on a canvas. It draws the moving playfield with a scene.
Start the screen
ti_gfx_begin sets SHR on, but it does not change the pixel bytes. Thus publish a clear canvas immediately.
static ti_surface canvas; /* zero-initialized */
extern const uint16_t game_palette[16];
ti_error start_screen(void)
{
ti_error e = ti_gfx_surface_create(&canvas, 320, 200);
if (!e) e = ti_gfx_begin(game_palette);
if (!e) e = ti_gfx_clear(&canvas, 0);
if (!e) e = draw_static_interface(&canvas);
if (!e) e = ti_gfx_present(&canvas, 0, 0); /* the only full publication */
return e;
}
A full publication writes 32,000 bytes. It takes more time than one 60 Hz frame: approximately 41 ms, measured on the emulator at vtw33. Publish the full screen only when all of the screen changes. Do not publish it in each frame.
Publish the changes
Draw into the canvas. Then publish the rectangles that you changed. The starter moves a square in this way: it erases the old square, draws the new square and publishes each one.
static int16_t x, y;
static void square(uint8_t color)
{
ti_rect r;
r.x = x; r.y = y; r.width = 16; r.height = 16;
ti_gfx_rect(&canvas, x, y, 16, 16, color);
ti_gfx_present(&canvas, &r, 0);
}
static void move_square(int16_t dx, int16_t dy)
{
square(0); /* erase the old position */
x += dx; y += dy;
square(11); /* draw the new position */
}
A publication extends the left and right edges of a rectangle to whole bytes. It takes the adjacent pixel from the canvas. Thus the canvas must hold the complete picture of each area that you publish.
Change a HUD value
Give each value a fixed length. Draw it with ti_gfx_text_opaque. The new digits fill their full 6x8 cells, so they cover the old digits without a clear rectangle. The area to publish is exactly the cells.
ti_digits writes a fixed number of digits with leading zeros. Keep a copy of the value on the screen, and do not draw a value that has not changed.
static uint32_t shown_score = 0xFFFFFFFFUL;
static void show_score(uint32_t score)
{
static const ti_rect cells = {250, 64, 7 * 6, 8};
char text[8];
if (score == shown_score) return;
shown_score = score;
ti_error_keep(ti_gfx_text_opaque(&canvas, 250, 64, ti_digits(text, score, 7), 9, 0));
ti_error_keep(ti_gfx_present(&canvas, &cells, 0));
}
For a value of two to eight characters, this takes approximately 1.9 ms at vtw33 and 1.5 ms at turbo-f122, measured on the emulator.
Animate with the palette
All 200 lines use one palette. One call writes its 32 bytes. A change to an entry changes the colour of all pixels that use it, without drawing.
Keep a copy of the palette. Keep for effects the entries that the art does not use. STARFALL pulses entries 14 and 15 for aimed bullets and shields. It flashes entry 0, the background, for bombs and hits.
static uint16_t palette[16]; /* copy of the game palette */
static const uint16_t pulse[8] = {0xF6B, 0xF7C, 0xF9D, 0xFBE, 0xFDF, 0xFBE, 0xF9D, 0xF7C};
static void animate(uint16_t step)
{
palette[15] = pulse[(step >> 1) & 7];
ti_error_keep(ti_gfx_palette(palette));
}
Sprites
For a small number of sprites on a picture, draw them into the canvas with ti_gfx_sprite. Copy the area under them back from a separate background surface with ti_gfx_blit. Publish the old and the new positions. These calls move pixels through resource reads and writes. Each pixel costs more than with rectangles and text.
For many sprites on a plain background, use a sprite scene. In each frame, add all sprites to the list in draw order. The scene compares the list with the last list. It composes again only the blocks of 16x8 pixels that changed. It does this in near memory, without a banked surface.
static void draw_playfield(void)
{
uint8_t i;
ti_scene_frame();
for (i = 0; i < STARS; ++i) ti_scene_add(star_x[i], star_y[i], IMG_DOT, star_color[i]);
for (i = 0; i < ENEMIES; ++i)
if (enemy_active[i]) ti_scene_add(enemy_x[i], enemy_y[i], enemy_image[i], 0);
ti_scene_add(ship_x, ship_y, IMG_SHIP, 0); /* added last, drawn on top */
ti_error_keep(ti_scene_draw());
}
Keep each object at the same list position in each frame if possible. A sprite that does not move and keeps its index costs no time. When the list is full, the scene ignores the remaining sprites. Thus add the important objects first, and decoration such as explosions last.
Use a canvas and a scene together
The scene does not know the other contents of the screen. When you publish the canvas over the area of the scene, tell the scene:
- If the area shows only the background of the scene, call
ti_scene_forget. The next draw draws all sprites and erases nothing. - If the area shows other contents, call
ti_scene_invalidate. The next draw paints all of the area again.
static void full_frame(void)
{
ti_error_keep(ti_gfx_clear(&canvas, 0)); /* the playfield area is background */
draw_hud(&canvas);
ti_error_keep(ti_gfx_present(&canvas, 0, 0));
ti_scene_forget();
}
A panel on the canvas over the playfield, for example a pause screen, stays on the screen while the game does not draw the scene. When the game continues, publish the frame again and call ti_scene_forget.
Tile maps that scroll
ti_gfx_map fills a viewport of a surface from a tile set and a map, at a camera position in pixels. Each scroll draws and publishes all of the viewport again. Thus the cost increases with the area of the viewport. Measure this cost before you use a large scrolled map. STARFALL moves its stars as sprites in a scene, and it does not scroll a picture.
Budgets and tearing
The screen has one page. A publication writes bytes while the Appletini continues to show frames. Thus the screen can show a large update when it is only partly written. Small updates do not show this problem often.
ti_gfx_budget sets limits for publications. A publication of more bytes than the byte budget stops before it writes. A publication that takes more ticks than the tick budget returns TI_E_TIMING after it is complete. Sprite scenes do not use these budgets. The list capacity and the area set the limit of their work.