Jump to content

Direct Drawing

From OpenBOR

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");

Each call creates a separate sprite outside the normal sprite cache. Load reusable artwork once during setup instead of loading it every frame.

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.

Inspecting a sprite

getgfxproperty() reads dimensions and other information from sprites, screens, and bitmaps.

int width   = getgfxproperty(sprite, "width");
int height  = getgfxproperty(sprite, "height");
int centerx = getgfxproperty(sprite, "centerx");
int centery = getgfxproperty(sprite, "centery");

Supported property names include width, height, centerx, centery, srcwidth, srcheight, palette, pixelformat, and pixel. The pixel property also needs X and Y arguments.

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

Coordinates outside the image return 0.

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.

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. The optional blend argument may be omitted.

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. Extra submissions are ignored once the queue is full. Text also consumes queue entries because each printed glyph is represented as a sprite.

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 also consume just one sprite each in the que. 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