Jump to content

Direct Drawing: Difference between revisions

From OpenBOR
No edit summary
Line 437: Line 437:
Each queued dot occupies one sprite-queue entry, but the 5,000-entry queue provides generous headroom. The 80-step curve above submits only 81 dots, and ordinary curves, outlines, particles, or object-heavy gameplay remain comfortably within practical capacity.
Each queued dot occupies one sprite-queue entry, but the 5,000-entry queue provides generous headroom. The 80-step curve above submits only 81 dots, and ordinary curves, outlines, particles, or object-heavy gameplay remain comfortably within practical capacity.


Subscreens are still valuable for persistent drawings or deliberately dense raster work. Use <code>drawdottoscreen()</code> to generate static procedural artwork once, retain it on a subscreen, then place the completed surface with <code>drawscreen()</code>. This recommendation is primarily about avoiding unnecessary regeneration, not treating the sprite queue as a scarce resource.
Subscreens are still valuable for persistent drawings or deliberately dense raster work. Use <code>drawdottoscreen()</code> to generate static procedural artwork once, retain it on a subscreen, then place the completed surface with <code>drawscreen()</code>.  
[[File:World Hereos Timeless Sine.png|none|frame|An example of creating shapes with dots. Calculating a sine wave and drawing as rows of single pixels with color graduation produces the smoothly curved line shown here.]]


=== drawline ===
=== drawline ===

Revision as of 23:50, 15 August 2026

OpenBOR direct drawing lets scripts place text, sprites, dots, lines, boxes, and complete off-screen surfaces into the video output. Two related paths are available: queued drawing, which joins the engine's normal depth-sorted render queue, and target-screen drawing, which changes a selected screen immediately.

This article describes the direct-draw API, resource ownership, ordering, color and blend handling, drawmethod control, and off-screen composition.

Parts of the system

Direct drawing uses four main objects.

Object Purpose
Sprite Image data loaded by loadsprite() or supplied by the engine.
Sprite queue Depth-sorted list used by drawsprite(), drawline(), drawbox(), and the other queued functions.
Screen Pixel surface used as a drawing target. The engine's main surface is vscreen; script-created surfaces are commonly called subscreens.
Drawmethod Shared transform and color state used for scaling, flipping, rotation, clipping, tinting, transparency, and other effects.

Queued drawing and target-screen drawing

Queued functions record a command for later rendering. The engine sorts those commands with its other render entries, then draws them during the normal video pass. Calls must be submitted again on every frame where the result should remain visible.

Target-screen functions write pixels immediately. Their results remain on a script-created screen until another operation overwrites the pixels, clearscreen() clears them, or free() releases the screen.

Queued function Immediate target-screen equivalent Key difference
drawstring() drawstringtoscreen() Queued text accepts a Z value; target-screen text follows call order.
drawsprite() drawspritetoscreen() Queued sprites accept Z and sort ID values; target-screen sprites do not.
drawdot() drawdottoscreen() Queued dots accept Z; target-screen dots are written immediately.
drawline() drawlinetoscreen() Queued lines accept Z; target-screen lines follow call order.
drawbox() drawboxtoscreen() Queued boxes accept Z; target-screen boxes follow call order.
drawscreen() Not applicable Places an already-drawn screen into the queue for composition.

Coordinates and order

Screen coordinates begin at the upper-left corner. X increases toward the right, while Y increases toward the bottom.

Queued entries sort by Z first and sort ID second. Larger values render later and therefore appear above entries with smaller values. Sort ID is available only to drawsprite(); it provides a stable secondary order when several sprites share the same Z value.

Immediate target-screen operations have no Z value. Later calls overwrite earlier pixels where the results overlap.

Colors and blend modes

Use rgbcolor() to create packed colors in the format expected by the current platform.

int red     = rgbcolor(255, 0, 0);
int green   = rgbcolor(0, 255, 0);
int blue    = rgbcolor(0, 0, 255);
int charcoal = rgbcolor(32, 36, 40);

Each component normally ranges from 0 to 255. Raw hexadecimal RGB literals are less portable because the packed channel order can differ by platform.

Shape functions and drawscreen() accept an optional blend mode.

Constant Effect
BLEND_MODE_MODEL Uses the applicable model or drawmethod behavior.
BLEND_MODE_NONE Copies the source without blending.
BLEND_MODE_ALPHA Screen-style lightening blend.
BLEND_MODE_ALPHA_NEGATIVE Multiply-style darkening blend.
BLEND_MODE_OVERLAY Overlay blend.
BLEND_MODE_HARDLIGHT Hard-light blend.
BLEND_MODE_DODGE Color-dodge blend.
BLEND_MODE_AVERAGE Equal average of source and destination.
int blend = openborconstant("BLEND_MODE_ALPHA");
drawline(20, 40, 180, 40, 1000, rgbcolor(255, 192, 64), blend);

Loading and releasing sprites

loadsprite

void sprite = loadsprite("data/sprites/interface/marker.gif");
void masked = loadsprite("data/sprites/interface/glow.gif",
                         "data/sprites/interface/glow_mask.gif");

loadsprite() places the requested image under OpenBOR's resource management and returns a script-owned handle. Load reusable artwork once during setup, retain the returned pointer, and reuse it for every frame that needs the artwork. Repeatedly loading and freeing the same file abuses the intended resource lifecycle and should be avoided.

free() tells OpenBOR that the script no longer needs the handle. The engine may release active sprite and mask data while retaining internal resource records and previously allocated cache capacity. This retention is deliberate and beneficial: it lets OpenBOR recognize and intelligently reuse sprite resources instead of blindly repeating every lookup, allocation, and load operation.

Memory reserved for those records and caches does not necessarily disappear immediately after free(). Rapid load/free cycles can expand the retained high-water capacity and make misuse look like a memory leak. That appearance does not indicate a defect in the resource manager - it indicates that short-lived loading is defeating a system designed around reuse.

Treat loaded sprites as retained resources rather than disposable draw commands. Load each sprite once, reuse it as needed, then call free() when its useful lifetime truly ends.

Transparent borders are trimmed while the sprite is prepared. Internal offsets preserve the visible artwork's position relative to the X and Y values passed to a draw function. Mask artwork should match the source image's dimensions and transparent layout.

Lifetime example

Store loaded pointers when multiple scripts need the same resource. Release every script-owned sprite with free() when its useful lifetime ends.

level.c

void main()
{
    void marker = loadsprite("data/sprites/interface/marker.gif");
    setglobalvar("direct_draw_marker", marker);
}

updated.c

void main()
{
    void marker = getglobalvar("direct_draw_marker");

    if(!isempty(marker)) {

        drawsprite(marker, 160, 100, 1000, 0);    
    }
}

endlevel.c

void main()
{
    void marker = getglobalvar("direct_draw_marker");

    if(!isempty(marker)) {

        free(marker);
        setglobalvar("direct_draw_marker", NULL());
    }
}

Never pass an engine-owned sprite or screen to free(). Only objects returned to the script by allocation or loading functions belong to the script.

Inspecting graphics objects

getgfxproperty() is a type-aware inspection function for sprites, screens, and bitmaps. Scripts can use the same interface to discover dimensions, drawing anchors, pixel format, palette data, and individual pixel values at runtime.

This capability makes graphics code independent of hard-coded asset dimensions. Interface layouts can size themselves around supplied artwork, effects can adapt to different screen formats, scripts can inspect generated subscreens, and tools can analyze image content without maintaining a duplicate description of the resource.

Syntax

Most properties use a graphics handle and property name.

int value = getgfxproperty(handle, property);

The pixel property also requires X and Y coordinates.

int value = getgfxproperty(handle, "pixel", x, y);

Properties

Property Valid objects Return Description
width Sprite, screen, bitmap Integer Drawable width in pixels. Sprite width describes the encoded image after transparent-edge trimming.
height Sprite, screen, bitmap Integer Drawable height in pixels. Sprite height describes the encoded image after transparent-edge trimming.
srcwidth Sprite, screen, bitmap Integer Source-width metadata stored with a sprite. Screens and bitmaps return their ordinary width. Sprite loaders may preserve a distinct source measurement when the resource representation requires one.
srcheight Sprite, screen, bitmap Integer Source-height metadata stored with a sprite. Screens and bitmaps return their ordinary height.
centerx Sprite, screen, bitmap Integer Horizontal drawing anchor stored by a sprite. The X coordinate passed to a sprite draw call positions this anchor rather than assuming the visible left edge. Screens and bitmaps return 0.
centery Sprite, screen, bitmap Integer Vertical drawing anchor stored by a sprite. The Y coordinate passed to a sprite draw call positions this anchor rather than assuming the visible top edge. Screens and bitmaps return 0.
palette Sprite, screen, bitmap Pointer Pointer to the palette owned by the graphics object. The result may be empty when the object has no attached palette. Never release this borrowed pointer with free().
pixelformat Sprite, screen, bitmap Integer Native storage format of the graphics object. The result corresponds to PIXEL_8, PIXEL_x8, PIXEL_16, or PIXEL_32.
pixel Sprite, screen, bitmap Integer Native pixel value at the supplied X and Y coordinates. Indexed surfaces return a palette index; 16-bit and 32-bit surfaces return their packed pixel value. Transparent, unavailable, unsupported, or out-of-bounds pixels return 0.

Dimensions and anchors

int width    = getgfxproperty(sprite, "width");
int height   = getgfxproperty(sprite, "height");
int source_w = getgfxproperty(sprite, "srcwidth");
int source_h = getgfxproperty(sprite, "srcheight");
int center_x = getgfxproperty(sprite, "centerx");
int center_y = getgfxproperty(sprite, "centery");

Width and height describe the pixels OpenBOR can draw from the encoded object. Source dimensions and center offsets expose the metadata needed to preserve alignment after transparent borders are trimmed. Reading these values lets reusable scripts accept artwork with different dimensions or anchor points without embedding asset-specific constants.

The next example builds a backing box around an arbitrary sprite.

int x = 40;
int y = 24;
int padding = 4;
int width = getgfxproperty(sprite, "width");
int height = getgfxproperty(sprite, "height");
int center_x = getgfxproperty(sprite, "centerx");
int center_y = getgfxproperty(sprite, "centery");
int left = x - center_x;
int top = y - center_y;
int back = rgbcolor(24, 28, 40);

drawbox(left - padding,
        top - padding,
        width + padding * 2,
        height + padding * 2,
        990,
        back);
drawsprite(sprite, x, y, 1000, 0);

Sprite anchor values can shift the visible image relative to x and y. Subtracting centerx and centery from the draw coordinates produces the visible image's upper-left position.

The red boxes seen are script drawn sprite borders. Sprite profiling is useful for a huge variety of effects - one example might be randomized fire sparks around a character when suffering from burn attacks.

Pixel format

int format = getgfxproperty(screen, "pixelformat");

if(format == openborconstant("PIXEL_32"))
{
    // The screen stores packed 32-bit pixels.
}

Checking the format before interpreting a pixel value is essential. Value 12 means palette entry 12 on an indexed surface, while the same integer represents a packed color value on a true-color surface.

Palette access

The palette property returns the live palette pointer attached to the graphics object. Palette functions can inspect or edit its color entries directly.

void palette = getgfxproperty(sprite, "palette");

if(!isempty(palette))
{
    int component = openborconstant("COLOR_COMPONENT_RED");
    int red = get_palette_property(palette, 12, component);
}

Palette pointers are borrowed views into their owning graphics objects. Keep the owner alive for as long as the pointer is used, and never pass the returned palette pointer to free(). Editing a live palette changes how its owner interprets indexed pixels, which enables runtime recoloring and diagnostic tools without duplicating the underlying image.

Pixel inspection

int sample = getgfxproperty(sprite, "pixel", 4, 6);

Pixel coordinates begin at the object's upper-left corner. Negative coordinates and coordinates at or beyond the reported width or height return 0. Since 0 can also represent transparency, palette index 0, or a packed black pixel, the return value alone cannot distinguish an out-of-bounds lookup from a valid zero-valued pixel. Check coordinates against width and height when that distinction matters.

Selective pixel inspection supports procedural masks, content-aware effects, palette analysis, runtime graphics diagnostics, and scripts that derive behavior from generated subscreens.

Tip: Large full-surface scans can be expensive, but you can usually cache them and avoid needless churn by sampling only the needed area. Used correctly this is an extremely powerful feature, such as seen here with a rain splatter effect on bodies. The underlying script profiles the upper surface of objects, and draws alternating offset dots to create a convincing rain effect tailored to the current sprite frame. Careful optimization allows the script to scan dozens of on screen objects and draw the effect with no frame loss.

https://www.youtube.com/watch?v=aAADyl40x-4

Queued drawing functions

drawsprite

drawsprite(sprite, x, y, z, sortid);

Queues a sprite at x, y, and z. The optional sortid defaults to 0. Current common drawmethod settings are copied into the queue entry when the call is made.

drawsprite(marker, 160, 100, 1000, 0);

drawstring

drawstring(x, y, font, value, z);

Queues text using the selected font index. The value is converted to text, and the optional Z value defaults to 0.

drawstring(12, 12, 0, "READY", 1000);
drawstring(12, 24, 0, getglobalvar("score"), 1000);

drawdot

drawdot(x, y, z, color, blend);

Queues one pixel at x, y, and z. The optional blend argument may be omitted.

One pixel sounds trivial, but it is the fundamental procedural drawing primitive. Every raster shape is ultimately a collection of pixels. Scripts can calculate coordinates from an equation, a data set, random input, entity positions, or any other runtime state, then submit a dot at each result. This makes drawdot() the foundation for graphics that have no dedicated built-in drawing command.

Technique How dots provide it
Curves Evaluate a parametric or polynomial curve at successive intervals and plot each resulting coordinate.
Circles and ellipses Convert successive angles into X and Y offsets around a center point.
Irregular outlines Generate coordinates from custom rules, sampled paths, or gameplay data.
Filled shapes Plot every pixel selected by a scan-line, distance, winding, or other inclusion test.
Graphs and waveforms Convert measured values into screen coordinates and plot the samples.
Particles and fields Plot independently calculated points for sparks, stars, dust, noise, gradients, or procedural textures.
Masks and diagnostic overlays Mark selected pixels, collision samples, anchors, paths, or regions for inspection.

The following example plots a quadratic Bézier curve. Three control points define the start, bend, and end. Increasing steps produces a smoother curve by plotting more samples.

int i;
int steps = 80;
int z = 1000;
int color = rgbcolor(255, 192, 64);
int blend = openborconstant("BLEND_MODE_NONE");
float t;
float inverse;
int x;
int y;

// Start point: 24, 120
// Control point: 160, 16
// End point: 296, 120
for(i = 0; i <= steps; i++)
{
    t = i * 1.0 / steps;
    inverse = 1.0 - t;

    x = inverse * inverse * 24
      + 2.0 * inverse * t * 160
      + t * t * 296;

    y = inverse * inverse * 120
      + 2.0 * inverse * t * 16
      + t * t * 120;

    drawdot(x, y, z, color, blend);
}

Closely spaced samples appear continuous at normal display resolutions. Curves with rapid changes or long spans need more samples. Connecting successive calculated coordinates with drawline() is another option when a guaranteed unbroken outline matters.

Each queued dot occupies one sprite-queue entry, but the 5,000-entry queue provides generous headroom. The 80-step curve above submits only 81 dots, and ordinary curves, outlines, particles, or object-heavy gameplay remain comfortably within practical capacity.

Subscreens are still valuable for persistent drawings or deliberately dense raster work. Use drawdottoscreen() to generate static procedural artwork once, retain it on a subscreen, then place the completed surface with drawscreen().

An example of creating shapes with dots. Calculating a sine wave and drawing as rows of single pixels with color graduation produces the smoothly curved line shown here.

drawline

drawline(x1, y1, x2, y2, z, color, blend);

Queues a line between two screen coordinates. The optional blend argument may be omitted.

int white = rgbcolor(255, 255, 255);
drawline(20, 30, 140, 30, 1000, white);

drawbox

drawbox(x, y, width, height, z, color, blend);

Queues a filled rectangle. The optional blend argument may be omitted.

int back = rgbcolor(16, 20, 28);
int blend = openborconstant("BLEND_MODE_ALPHA");
drawbox(8, 8, 144, 36, 990, back, blend);

drawscreen

drawscreen(screen, x, y, z, blend);

Queues a complete screen at the requested position and depth. The optional blend argument changes more than the blend mode: supplying it also makes zero-valued black pixels transparent. Omitting it preserves the current common drawmethod behavior, so black pixels remain opaque unless background transparency is already enabled there.

// Opaque zero-valued background unless the common drawmethod says otherwise.
drawscreen(panel, 0, 160, 1000);

// Zero-valued background becomes transparent.
int blend = openborconstant("BLEND_MODE_ALPHA");
drawscreen(panel, 0, 160, 1000, blend);

Screens and off-screen composition

allocscreen

void screen = allocscreen(width, height);

Creates a script-owned 32-bit screen and clears its pixels to zero. Internal allocation rounds the requested width down to a multiple of four, so widths divisible by four are the safest choice.

void panel = allocscreen(320, 80);

Use openborvariant("vscreen") to obtain the engine-owned main video surface. Never release vscreen with free().

clearscreen

clearscreen(screen);

Fills the target's pixel buffer with zero. Script-created screens retain previous drawing until cleared or overwritten.

Target-screen functions

drawstringtoscreen(screen, x, y, font, value);
drawspritetoscreen(sprite, screen, x, y);
drawdottoscreen(screen, x, y, color, blend);
drawlinetoscreen(screen, x1, y1, x2, y2, color, blend);
drawboxtoscreen(screen, x, y, width, height, color, blend);

The target screen is the first argument for text and shapes. drawspritetoscreen() is the exception: its sprite comes first and its target screen comes second. Blend arguments are required by the target-screen dot, line, and box functions; use BLEND_MODE_NONE when no blending is wanted.

drawspritetoscreen() and the target-screen shape functions use the current common drawmethod. drawstringtoscreen() prints directly with the selected font and does not use that drawmethod.

Composition example

The following setup creates a retained panel, redraws its contents, then places the result into the normal Z-sorted queue.

level.c

void main()
{
    void panel = allocscreen(320, 80);
    void icon = loadsprite("data/sprites/interface/icon.gif");

    setglobalvar("direct_draw_panel", panel);
    setglobalvar("direct_draw_icon", icon);
}

updated.c

void main()
{
    void panel = getglobalvar("direct_draw_panel");
    void icon = getglobalvar("direct_draw_icon");
    int none = openborconstant("BLEND_MODE_NONE");
    int alpha = openborconstant("BLEND_MODE_ALPHA");

    clearscreen(panel);
    drawboxtoscreen(panel, 4, 4, 312, 72, rgbcolor(24, 28, 40), none);
    drawlinetoscreen(panel, 4, 4, 315, 4, rgbcolor(224, 224, 255), none);
    drawspritetoscreen(icon, panel, 12, 12);
    drawstringtoscreen(panel, 52, 16, 0, "MISSION STATUS");

    // Supplying a blend also treats the cleared black area as transparent.
    drawscreen(panel, 0, 160, 1000, alpha);
}

endlevel.c

void main()
{
    void panel = getglobalvar("direct_draw_panel");
    void icon = getglobalvar("direct_draw_icon");

    if(!isempty(panel)) free(panel);
    if(!isempty(icon)) free(icon);

    setglobalvar("direct_draw_panel", NULL());
    setglobalvar("direct_draw_icon", NULL());
}

Common drawmethod

The common drawmethod is global rendering state shared by compatible direct-draw calls. Obtain it and the read-only default state through system variants.

void common = openborvariant("drawmethod_common");
void defaults = openborvariant("drawmethod_default");

Use set_drawmethod_property() with named property constants. Useful properties include the following groups.

Purpose Property constants
Enable and flip flags DRAWMETHOD_PROPERTY_CONFIG
Position and pivot DRAWMETHOD_PROPERTY_CENTER_X, DRAWMETHOD_PROPERTY_CENTER_Y
Scale and rotation DRAWMETHOD_PROPERTY_SCALE_X, DRAWMETHOD_PROPERTY_SCALE_Y, DRAWMETHOD_PROPERTY_ROTATE
Blend and fill DRAWMETHOD_PROPERTY_ALPHA, DRAWMETHOD_PROPERTY_FILL_COLOR
Color channels and tint DRAWMETHOD_PROPERTY_CHANNEL_RED, DRAWMETHOD_PROPERTY_CHANNEL_GREEN, DRAWMETHOD_PROPERTY_CHANNEL_BLUE, DRAWMETHOD_PROPERTY_TINT_COLOR, DRAWMETHOD_PROPERTY_TINT_MODE
Clip rectangle DRAWMETHOD_PROPERTY_CLIP_POSITION_X, DRAWMETHOD_PROPERTY_CLIP_POSITION_Y, DRAWMETHOD_PROPERTY_CLIP_SIZE_X, DRAWMETHOD_PROPERTY_CLIP_SIZE_Y
Repetition and span DRAWMETHOD_PROPERTY_REPEAT_X, DRAWMETHOD_PROPERTY_REPEAT_Y, DRAWMETHOD_PROPERTY_SPAN_X, DRAWMETHOD_PROPERTY_SPAN_Y
Palette remap DRAWMETHOD_PROPERTY_COLORSET_INDEX, DRAWMETHOD_PROPERTY_COLORSET_TABLE
Water effects DRAWMETHOD_PROPERTY_WATER_MODE and the perspective, size, wave, speed, and time properties.

Configuration flags are combined with the bitwise OR operator.

Flag Purpose
DRAWMETHOD_CONFIG_ENABLED Enables common drawmethod processing.
DRAWMETHOD_CONFIG_BACKGROUND_TRANSPARENCY Treats zero-valued background pixels as transparent.
DRAWMETHOD_CONFIG_FLIP_X Flips horizontally.
DRAWMETHOD_CONFIG_FLIP_Y Flips vertically.
DRAWMETHOD_CONFIG_FLIP_ROTATE Changes flip handling for rotated output.

Scale values use 256 as 100 percent. The next example flips a sprite horizontally, scales it to 150 percent, queues it, then restores the shared state.

void common = openborvariant("drawmethod_common");
void defaults = openborvariant("drawmethod_default");
int config = openborconstant("DRAWMETHOD_CONFIG_ENABLED")
           | openborconstant("DRAWMETHOD_CONFIG_FLIP_X");

copy_drawmethod(common, defaults);
set_drawmethod_property(common,
                        openborconstant("DRAWMETHOD_PROPERTY_CONFIG"),
                        config);
set_drawmethod_property(common,
                        openborconstant("DRAWMETHOD_PROPERTY_SCALE_X"),
                        384);
set_drawmethod_property(common,
                        openborconstant("DRAWMETHOD_PROPERTY_SCALE_Y"),
                        384);

drawsprite(sprite, 160, 100, 1000, 0);
copy_drawmethod(common, defaults);

Queued entries receive a copy of the common drawmethod at submission time. Restoring the common state immediately after drawsprite() therefore does not undo the queued sprite's transform.

Legacy scripts may use setdrawmethod() and changedrawmethod(). Named drawmethod properties are clearer for new code and reduce dependence on positional arguments or string property names.

Queue control

drawspriteq

drawspriteq(screen, newonly, minz, maxz, dx, dy);

Renders entries from the current sprite queue to a selected screen. Passing an empty first argument targets vscreen. Optional arguments default to all entries, the full Z range, and zero offset.

void snapshot = getglobalvar("direct_draw_snapshot");
drawspriteq(snapshot, 0, 500, 1500, 0, 0);

The function does not remove rendered entries. Its newonly option matters when the queue is locked; ordinary scripts normally use 0.

clearspriteq

clearspriteq();

Clears the active sprite queue, or only its new portion while the queue is locked. Normal game rendering shares this queue with backgrounds, entities, shadows, and interface elements, so routine scripts should not call clearspriteq(). Reserve it for controlled rendering workflows where removing every pending entry is intentional.

Script timing

Queued and immediate calls behave differently around the engine's frame clear.

Script stage Direct-draw behavior
update.c Suitable for queueing commands. Immediate writes to vscreen are erased by the main screen clear that follows.
Native render setup Clears vscreen and queues backgrounds, entities, shadows, and status elements.
updated.c Suitable for queueing with final knowledge of game state. Immediate writes to vscreen survive because the main clear has already happened.
Queue render Sorts entries by Z and sort ID, then draws them to vscreen.
Frame end Presents the video output and clears the queue for the next frame.

Queued calls made from either update.c or updated.c still take part in the final Z sort. Direct writes to a retained subscreen can be made at other times because that surface is not part of the automatic main-screen clear.

Limits and performance

The current sprite queue holds up to 5,000 entries, providing far more capacity than ordinary game objects require. Normal gameplay does not approach this limit, including object-heavy or bullet-hell designs. Reaching it is mainly possible when procedural scripts intentionally emit thousands of individual dots, lines, boxes, or text glyphs during one frame. Extra submissions are ignored only after the full capacity is reached.

Sprite loading and screen allocation are setup operations, not frame-by-frame drawing operations. Reusing allocations avoids file access, decoding work, heap churn, and leak risk.

Large subscreens cost memory and fill bandwidth, but a completed surface consumes only one sprite-queue entry when submitted with drawscreen(). Keep off-screen surfaces near the size of the content being composed, use widths divisible by four, and redraw only when their contents need to change.

Practical guidance

  • Load script-owned sprites once, retain their pointers, and release them with free().
  • Use queued functions when Z ordering with entities or interface elements matters.
  • Use target-screen functions to build reusable panels, effects, masks, or snapshots.
  • Clear a subscreen before rebuilding it unless retained pixels are intentional.
  • Create colors with rgbcolor() and select blends with named constants.
  • Treat the common drawmethod as shared state: configure it, submit the draw, then restore the default state.
  • Supply drawscreen()'s blend argument when zero-valued background pixels should become transparent.
  • Keep immediate vscreen drawing in a stage after the engine's main clear, such as updated.c.
  • Avoid clearspriteq() during normal gameplay because native rendering uses the same queue.
  • Resubmit queued artwork every frame where it should remain visible.

See also