Banjo API 1.0.0-rc.2
Low-level C99 game development API
Loading...
Searching...
No Matches
drawing_2d.c

Tutorial: Drawing 2D primitives onto the framebuffer.

Tutorial: Drawing 2D primitives onto the framebuffer.Building on start.c, this tutorial walks through every primitive Banjo offers for software rendering: pixels, polylines, triangles (outlined and filled), rectangles (outlined and filled), and circles (outlined and filled). The same idioms scale up to any retro-style 2D game.

What you'll learn

Why the bitmap-native pixel encoding matters

Banjo can run with framebuffers in different pixel modes (XRGB8888, RGB565, indexed palettes…). A literal 0x00FF0000 is red in XRGB8888 but means something else in any other mode. The bj_make_bitmap_pixel call asks the bitmap to pack the value itself, so the same drawing code works regardless of mode. The Pixel Definition topic explains the encoding in more detail.

Walkthrough

1. Initialise the same way as start.c

The setup callback opens a window, registers a draw callback, and installs the ESC-to-quit shortcut. Same shape as start.c; the interesting part is what comes next.

2. Draw once, scale on damage

The draw callback is fired by the backend whenever the window has pending damage. Since nothing in this scene animates, setup renders the scene a single time into a fixed-size off-screen bitmap (the canvas), and the draw callback only scales that canvas onto the framebuffer. The callback then runs on each uncover / resize / system damage event, rescaling the same canvas to the window's current size. A real game would invalidate on every step iteration; we don't.

3. The pattern: clear, draw, repeat

Every primitive function in this tutorial writes directly into the bitmap. They don't queue commands; they don't track state. Inside the draw callback the discipline is:

  1. bj_clear_bitmap to wipe to a known background.
  2. Issue every draw call you want for this frame.

The backend takes care of pushing the result to the window after the callback returns. To request the next paint (e.g. on animation), call bj_invalidate_window from your step.

4. Bounds are your problem, so we fix the resolution

The Drawing primitives don't clip: they assume the coordinates you pass lie inside the bitmap. Going out of bounds is undefined behaviour. The tutorial's vertex tables are pre-sized to fit a 500×500 area, so all the drawing targets a fixed 500×500 canvas rather than the window's framebuffer directly. That keeps the hardcoded coordinates valid no matter how the user resizes the window.

The window is created with BJ_WINDOW_FLAG_RESIZABLE, and its framebuffer changes size as you drag it. We bridge the two with bj_blit_stretched, which scales the fixed canvas onto the framebuffer and clips to its bounds. This is the fixed-internal- resolution pattern the bj_window_resize_fn documentation describes: keep your own bitmap untouched and recompute how it scales onto the framebuffer at draw time. If instead you want crisp drawing at the native window size, drop the canvas and draw into the framebuffer from bj_set_resize_callback, clipping or constraining your own coordinates.

5. Filled vs outlined

Most shapes come in pairs: outlined (bj_draw_X) and filled (bj_draw_filled_X). The example draws both flavours of rectangle, triangle, and circle side-by-side so you can compare. Internally, the outlined variants are line-based; the filled variants do scan- line conversion. They cost roughly the same for small shapes.

What's next

  • drawing_text.c: text rendering with the built-in 8×8 font.
  • shaders.c: per-pixel shader-style colour processing.
  • load_bmp.c: bring images in from disk.
  • The Drawing topic for the full primitive reference.
#include <banjo/app.h>
#include <banjo/bitmap.h>
#include <banjo/draw.h>
#include <banjo/event.h>
#include <banjo/log.h>
#include <banjo/main.h>
#include <banjo/pixel.h>
#include <banjo/system.h>
#include <banjo/window.h>
// The scene is composed at a fixed internal resolution: every draw call below
// is sized for a 500x500 area. The window itself is resizable, so we render
// the scene once into this off-screen canvas and scale that onto the
// framebuffer each frame, rather than drawing straight into a framebuffer
// whose size changes (which would push the fixed coordinates out of bounds).
enum { CANVAS_W = 500, CANVAS_H = 500 };
static bj_bitmap* canvas = 0;
static void on_draw(
struct bj_window* w,
struct bj_render_target* target,
const struct bj_rect* dirty,
void* user_data
);
void draw(bj_bitmap* bmp) {
// Clear the bitmap to black before drawing.
// Create colours in the bitmap's native pixel format. Always use
// bj_make_bitmap_pixel() rather than hardcoding values, as the format
// varies by platform and configuration.
const uint32_t color_red = bj_make_bitmap_pixel(bmp, 0xFF, 0x00, 0x00);
const uint32_t color_cyan = bj_make_bitmap_pixel(bmp, 0x7F, 0xFF, 0xD4);
const uint32_t color_white = bj_make_bitmap_pixel(bmp, 0xFF, 0xFF, 0xFF);
// Draw individual pixels. bj_put_pixel() sets a single pixel at (x, y).
// This is the most basic drawing operation.
for (size_t x = 10; x < 490; ++x) {
if (x % 7 == 0) {
bj_put_pixel(bmp, x, 10, color_red);
}
}
// Draw a polyline (connected line segments). The last parameter (BJ_TRUE)
// closes the shape by connecting the last point back to the first.
// This draws a banjo outline.
int poly_x[] = { 100, 95, 95, 100, 100, 95, 75, 75, 95, 120, 140, 140, 120, 115, 115, 120, 120, 115, };
int poly_y[] = { 20, 25, 50, 55, 100, 100, 120, 145, 165, 165, 145, 120, 100, 100, 55, 50, 25, 20, };
bj_draw_polyline(bmp, 18, poly_x, poly_y, BJ_TRUE, color_cyan);
// Draw outlined triangles by indexing into a vertex array. This technique
// is common in graphics programming and efficiently reuses vertex data.
// Here we draw 13 triangles forming a fox shape.
int verts[][2] = {
{330, 270}, {270, 210}, {210, 270}, {210, 150}, {390, 210}, {450, 270},
{450, 150}, {180, 330}, {270, 390}, {390, 390}, {480, 330}, {330, 450},
{300, 480}, {360, 480},
};
size_t tris[13][3] = {
{0, 1, 2}, {0, 2, 3}, {0, 4, 5}, {0, 1, 4}, {4, 6, 5}, {2, 8, 7},
{0, 8, 2}, {0, 5, 9}, {9, 5, 10}, {8, 9, 11}, {8, 11, 12},
{9, 13, 11}, {11, 12, 13},
};
for (size_t t = 0; t < 13; ++t) {
verts[tris[t][0]][0], verts[tris[t][0]][1],
verts[tris[t][1]][0], verts[tris[t][1]][1],
verts[tris[t][2]][0], verts[tris[t][2]][1],
color_white
);
}
// Draw a checkerboard pattern using filled rectangles. The bj_rect
// structure defines a rectangle's position and size.
bj_rect board = {.w = 10, .h = 10,};
for(size_t y = 0 ; y < 8 ; ++y) {
for(size_t x = 0 ; x < 8 ; ++x) {
board.x = 200 + x * board.w;
board.y = 50 + y * board.h;
// XOR determines checkerboard pattern (alternating squares).
if((x ^ y) & 1) {
bj_draw_filled_rectangle(bmp, &board, color_red);
}
}
}
// Draw an outline around the entire checkerboard. Note the difference
// between bj_draw_rectangle (outline) and bj_draw_filled_rectangle (solid).
&(bj_rect) {.x = 200, .y = 50, .w = 80, .h = 80,},
color_cyan
);
// Draw concentric circles with alternating colours. bj_draw_filled_circle()
// takes centre (x, y), radius, and colour.
for (int r = 80; r > 0; r -= 20) {
bj_draw_filled_circle(bmp, 100, 400, r, (r/20) % 2 ? color_red : color_white);
}
// Draw filled triangles for a simple mountain scene. Filled triangles are
// drawn with bj_draw_filled_triangle(), taking three vertices and a colour.
const uint32_t color_dark_gray = bj_make_bitmap_pixel(bmp, 0x50, 0x50, 0x50);
const uint32_t color_gray = bj_make_bitmap_pixel(bmp, 0x80, 0x80, 0x80);
const uint32_t color_light_gray = bj_make_bitmap_pixel(bmp, 0xB0, 0xB0, 0xB0);
bj_draw_filled_triangle(bmp, 250, 400, 200, 480, 300, 480, color_gray);
bj_draw_filled_triangle(bmp, 300, 420, 250, 480, 350, 480, color_dark_gray);
bj_draw_filled_triangle(bmp, 350, 390, 300, 480, 400, 480, color_light_gray);
}
// Static drawing: banjo asks, we present. The scene was rendered into the
// canvas once in setup; here we just scale that fixed 500x500 image onto the
// window's framebuffer at whatever size the window currently is. Passing 0
// for both areas means "whole source -> whole destination", and
// bj_blit_stretched clips to the destination, so this is always in bounds no
// matter how the window is resized.
static void on_draw(
struct bj_window* w,
struct bj_render_target* target,
const struct bj_rect* dirty,
void* user_data
) {
(void)w; (void)dirty; (void)user_data;
}
static void* setup(struct bj_app* app, void* init_data) {
(void)init_data;
bj_quit_app(app, 1);
return 0;
}
// Fixed-resolution canvas: the scene is static, so render it once here.
// on_draw then only scales it onto the framebuffer.
if (canvas == 0) {
bj_quit_app(app, 1);
return 0;
}
window = bj_bind_window("2D Drawing", 100, 100, CANVAS_W, CANVAS_H,
return 0;
}
static void step(struct bj_app* app, struct bj_tick_info tick, void* user_data) {
(void)tick;
(void)user_data;
bj_quit_app(app, 0);
}
}
static void teardown(struct bj_app* app, void* user_data) {
(void)user_data;
bj_end();
}
int main(int argc, char* argv[]) {
(void)argc; (void)argv;
return bj_run_app(setup, step, 0, teardown, 0);
}
Application lifecycle: callback-driven setup, step, and teardown.
int main(int argc, char *argv[])
Definition audio_pcm.c:177
static void step(struct bj_app *app, struct bj_tick_info tick, void *user_data)
Definition audio_pcm.c:144
static void teardown(struct bj_app *app, void *user_data)
Definition audio_pcm.c:170
static void * setup(struct bj_app *app, void *init_data)
Definition audio_pcm.c:107
Header file for Bitmap type.
static void on_draw(struct bj_window *w, struct bj_render_target *target, const struct bj_rect *dirty, void *user_data)
Definition bitmap_blit.c:32
bj_window * window
Definition bitmap_blit.c:24
Header file for Bitmap drawing functions.
static bj_bitmap * canvas
Definition drawing_2d.c:124
void draw(bj_bitmap *bmp)
Definition drawing_2d.c:133
Sytem event management API.
int bj_run_app(bj_app_setup_fn setup, bj_app_step_fn step, bj_app_fixed_step_fn fixed_step, bj_app_teardown_fn teardown, void *init_data)
Drive the application lifecycle.
void bj_quit_app(struct bj_app *app, int exit_code)
Signal the given application to exit on the next iteration.
Timing snapshot handed to the step and fixed-step callbacks.
Definition app.h:106
void bj_clear_bitmap(struct bj_bitmap *bitmap)
Fills the entire bitmap with the clear colour.
bj_bool bj_blit_stretched(const struct bj_bitmap *src, const struct bj_rect *src_area, struct bj_bitmap *dst, const struct bj_rect *dst_area, enum bj_blit_op op)
Stretched bitmap blitting (nearest neighbor).
uint32_t bj_make_bitmap_pixel(struct bj_bitmap *bitmap, uint8_t red, uint8_t green, uint8_t blue)
Returns an opaque value representing a pixel colour, given its RGB composition.
struct bj_bitmap * bj_create_bitmap(size_t width, size_t height, enum bj_pixel_mode mode, size_t stride)
Creates a new struct bj_bitmap with the specified width and height.
void bj_put_pixel(struct bj_bitmap *bitmap, size_t x, size_t y, uint32_t value)
Change the pixel colour at given coordinate.
void bj_destroy_bitmap(struct bj_bitmap *bitmap)
Deletes a struct bj_bitmap object and releases associated memory.
@ BJ_BLIT_OP_COPY
Copy source to destination (fast path when formats match)
Definition bitmap.h:93
struct bj_render_target bj_render_target
Definition api.h:346
struct bj_bitmap bj_bitmap
Definition api.h:328
struct bj_window bj_window
Definition api.h:354
#define BJ_TRUE
Boolean true value (1).
Definition api.h:275
void bj_draw_rectangle(struct bj_bitmap *bitmap, const struct bj_rect *area, uint32_t pixel)
Draws a rectangle in the given bitmap.
void bj_draw_polyline(struct bj_bitmap *bitmap, size_t count, const int *x, const int *y, bj_bool loop, uint32_t color)
Draw a polyline from C-style coordinate arrays.
void bj_draw_triangle(struct bj_bitmap *bitmap, int x0, int y0, int x1, int y1, int x2, int y2, uint32_t color)
Draws the edges of a triangle given its 3 corners.
void bj_draw_filled_rectangle(struct bj_bitmap *bitmap, const struct bj_rect *area, uint32_t pixel)
Draws a filled rectangle in the given bitmap.
void bj_draw_filled_circle(struct bj_bitmap *bitmap, int cx, int cy, int radius, uint32_t color)
Draw a filled circle onto a bitmap.
void bj_draw_filled_triangle(struct bj_bitmap *bitmap, int x0, int y0, int x1, int y1, int x2, int y2, uint32_t color)
Draws a filled triangle given its 3 corners.
void bj_dispatch_events(void)
Poll and dispatch all pending events.
void bj_close_on_escape(struct bj_window *window, const struct bj_key_event *event, void *user_data)
Handle the ESC key to close a window.
bj_key_callback_fn bj_set_key_callback(bj_key_callback_fn callback, void *user_data)
Set the global callback for keyboard key events.
uint16_t w
Width in pixels.
Definition rect.h:36
uint16_t h
Height in pixels.
Definition rect.h:37
int16_t y
Y coordinate of the top-left corner (pixels, can be negative).
Definition rect.h:35
int16_t x
X coordinate of the top-left corner (pixels, can be negative).
Definition rect.h:34
Axis-aligned rectangle: a top-left corner plus a width and height.
Definition rect.h:33
@ BJ_PIXEL_MODE_XRGB8888
32bpp RGB
Definition pixel.h:94
bj_bool bj_begin(int systems, struct bj_error **error)
Initialises the system.
void bj_end(void)
De-initialises the system.
@ BJ_VIDEO_SYSTEM
Definition system.h:81
void bj_set_draw_callback(struct bj_window *window, bj_window_draw_fn fn, void *user_data)
Register the redraw callback for window.
static void bj_invalidate_window(struct bj_window *window)
Mark the whole window as needing a repaint.
Definition window.h:470
struct bj_window * bj_bind_window(const char *title, uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t flags, struct bj_error **error)
Create a new struct bj_window with the specified attributes.
struct bj_bitmap * bj_render_target_bitmap(struct bj_render_target *target)
Reach the software framebuffer behind a render target.
bj_bool bj_should_close_window(struct bj_window *window)
Get the close flag state of a window.
void bj_unbind_window(struct bj_window *window)
Deletes a struct bj_window object and releases associated memory.
@ BJ_WINDOW_FLAG_RESIZABLE
User may resize the window.
Definition window.h:190
Logging utility functions.
Portable main() replacement with platform-aware entry shim.
Header file for general pixel manipulation facilities.
#define CANVAS_W
Definition shaders.c:26
#define CANVAS_H
Definition shaders.c:27
Header file for system interactions.
Header file for bj_window type.