Modules

Simp

moduleSource

Available on macOS, Linux, Windows

#import "Simp";

A small 2D renderer on OpenGL: coloured shapes, textures, text and render targets, drawn through immediate-mode calls.

Make a window with Window_Creation, choose it with set_render_target, then each frame call update_window, clear_render_target, draw, and swap_buffers. Pick a shader first (set_shader_for_color or set_shader_for_images), then draw with immediate_quad, or send single corners with immediate_vertex. load_font and draw_text put text on screen. Textures come from texture_load_from_file or texture_load_from_memory.

The default coordinate system has its origin at the bottom left with y growing up; pass .LEFT_HANDED to set_render_target for the top left with y growing down.

Example↗
#import "Window_Creation";
#import "Input";

window := create_window(800, 600, "simp");
set_render_target(window);
font := load_font("", "Roboto-Regular.ttf", 24);

while true {
    update_window_events();
    for events_this_frame if it.type == .QUIT return;
    update_window(window);

    clear_render_target(0.1, 0.1, 0.15, 1);
    set_shader_for_color();
    immediate_quad(100, 100, 300, 200, .{0.9, 0.3, 0.2, 1});
    draw_text(font, 100, 300, "Hello");
    swap_buffers(window);
}

This one is only checked to compile, not run.

Module parameters#

render_api := Render_API.OPENGL
The graphics API to draw with. .NONE draws nothing.

Procedures#

Images#

(tf: Texture_Format, w: int, h: int) -> (int, stride: int)

The bytes an image of this format and size takes, and the bytes in one row (or one row of blocks).

size, stride := get_image_size(.RGBA8, 10, 5);
assert(size == 200 && stride == 40);

size, stride = get_image_size(.BC1, 10, 5);  // 3 x 2 blocks of 8 bytes
assert(size == 48 && stride == 24);
↗
(bitmap: *Bitmap, w: s32, h: s32, _format: Texture_Format)

Gives bitmap pixel storage of the given size and format, freeing what it held. The bitmap owns the memory: free it with deinit.

bitmap: Bitmap;
bitmap_alloc(*bitmap, 8, 8, .R8);
defer deinit(*bitmap);
assert(bitmap.data.count == 64);
↗
(bitmap: *Bitmap)

Frees the bitmap's pixels if it owns them, and empties it.

Mipmaps#

Image codecs#

(bitmap: *Bitmap, filepath: string) -> bool

Reads an image file into bitmap. Returns false when the file is missing or is not an image.

bitmap: Bitmap;
if !bitmap_load(*bitmap, "sprite.png") return;
defer deinit(*bitmap);

This one is only checked to compile, not run.

(bitmap: *Bitmap, memory: [] u8) -> bool

Like bitmap_load, for an image that is already in memory.

#import "Extensions/Image";

png := encode_png(make_image(2, 2, .RGB8));
defer free(png.data);

bitmap: Bitmap;
assert(bitmap_load_from_memory(*bitmap, png));
defer deinit(*bitmap);
assert(bitmap.width == 2 && bitmap.format == .RGB8);
↗
(bitmap: *Bitmap, filepath: string, save_alpha := false) -> bool

Writes bitmap to an image file. Returns false for any other extension, for a bitmap that is not RGB8 or RGBA8, or when the write fails.

bitmap: Bitmap;
bitmap_alloc(*bitmap, 16, 16, .RGB8);
defer deinit(*bitmap);
bitmap_save(*bitmap, "out.png");

This one is only checked to compile, not run.

Loaded fonts#

(_page_size_x: s32 = -1, _page_size_y: s32 = -1)

Starts FreeType and optionally sets the size of the glyph atlas pages (at least 64 by 64). Fonts start it on first use.

Dynamic fonts#

(path: string, name: string, pixel_height: int, pixel_scale := 1.0) -> *Dynamic_Font
(memory: [] u8, pixel_height: int, pixel_scale := 1.0) -> *Dynamic_Font

Loads a font file at a size, reusing the font if it was loaded at that size before. Returns null if the file can't be read.

(path: string, basename: string, pixel_height: int, pixel_scale := 1.0) -> font: *Dynamic_Font

Loads a font file at one size. Returns null when the file cannot be read.

font := load_font("fonts", "Roboto-Regular.ttf", 24);
if !font return;

This one is only checked to compile, not run.

(font: *Dynamic_Font)

Starts a run of text for append_prepare_text. Use it, with end_prepare_text, to join pieces that prepare_text can't take in one string.

(font: *Dynamic_Font)

Closes the run and lays its glyphs out as font.current_quads, with the pen at the origin and the baseline at y = 0. draw_prepared_text draws the same run wherever you like.

GL textures#

(map: *Texture, filename: string, srgb := false, build_mipmaps := false) -> bool

Reads an image file and uploads it as map. Returns false when it cannot read or decode the file.

texture: Texture;
if !texture_load_from_file(*texture, "sprite.png", srgb = true, build_mipmaps = true) return;

This one is only checked to compile, not run.

(texture: *Texture, memory: [] u8, srgb := false, build_mipmaps := false) -> bool

Like texture_load_from_file, for an image that is already in memory.

#import "File";

file := read_entire_file("sprite.png");
texture: Texture;
if !texture_load_from_memory(*texture, cast([] u8) file) return;

This one is only checked to compile, not run.

(width: s32, height: s32, format: Texture_Format, flags: Texture_Flags = 0) -> Texture

Makes an empty texture that can be drawn into: make it current with set_render_target, then draw as usual.

#import "Window_Creation";

window := create_window(800, 600, "simp");
target := texture_create_render_target(512, 512, .RGBA8);
set_render_target(*target);
// ... draw ...
set_render_target(window);
// now draw `target` like any texture

This one is only checked to compile, not run.

GL shaders#

(shader_text: string) -> GLuint

Compiles a shader from its source text and returns the GL program, or 0 if it doesn't compile.

GL renderer#

(window: Window_Type, coords := Simp_Coordinate_System.RIGHT_HANDED)
(texture: *Texture, coords := Simp_Coordinate_System.RIGHT_HANDED)

Chooses where drawing goes, and what the coordinates mean. Anything drawn so far is flushed first.

#import "Window_Creation";

window := create_window(800, 600, "simp");
set_render_target(window, .LEFT_HANDED);

This one is only checked to compile, not run.

(window: Window_Type) -> (width: s32, height: s32)

The size of the window's framebuffer in device pixels.

#import "Window_Creation";

window := create_window(800, 600, "simp");
width, height := get_pixel_dimensions(window);

This one is only checked to compile, not run.

(window: Window_Type) -> float

Device pixels per window coordinate unit (2 on a Retina display).

#import "Window_Creation";

window := create_window(800, 600, "simp");
scale := get_pixel_scale(window);  // 2 on a Retina display

This one is only checked to compile, not run.

(r: float, g: float, b: float, a: float)

Fills the current render target with one colour, and clears its depth and stencil.

clear_render_target(0.1, 0.1, 0.15, 1);

This one is only checked to compile, not run.

(p0: Vector2, p1: Vector2)
(x0: s32, y0: s32, x1: s32, y1: s32)

Limits drawing to a box, in the coordinates of the current render target.

set_scissor(.{10, 10}, .{110, 60});
// ... draw: only this box shows ...
clear_scissor();

This one is only checked to compile, not run.

()

Draws the batch now. Switching shader or texture, swap_buffers and a full batch do this on their own.

(position: Vector3, color_scale: Vector4, uv: Vector2 = .{}, normal: Vector3 = .{})

Adds one corner to the batch. Every three corners make a triangle, and the batch is drawn when it fills or when something forces a flush.

set_shader_for_color();
#import "Math";

red := Vector4.{1, 0, 0, 1};
immediate_vertex(.{100, 100, 0}, red);
immediate_vertex(.{200, 100, 0}, red);
immediate_vertex(.{150, 200, 0}, red);  // a triangle

This one is only checked to compile, not run.

(font: *Dynamic_Font, x: s64, y: s64, text: string, color := Vector4.{ 1, 1, 1, 1 }, effects: Font_Effects = 0) -> width: s64

Draws text in one line and returns its width in pixels.

font := load_font("", "Roboto-Regular.ttf", 24);
width := draw_text(font, 20, 40, "Score: 120", .{1, 1, 0, 1});

This one is only checked to compile, not run.

Immediate geometry#

(x0: float, y0: float, x1: float, y1: float, color: Vector4)
(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, color := Vector4.{ 1, 1, 1, 1 }, uv0 := Vector2.{ 0, 0 }, uv1 := Vector2.{ 1, 0 }, uv2 := Vector2.{ 1, 1 }, uv3 := Vector2.{ 0, 1 })
(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, c0: Vector4, c1: Vector4, c2: Vector4, c3: Vector4, uv0 := Vector2.{ 0, 0 }, uv1 := Vector2.{ 1, 0 }, uv2 := Vector2.{ 1, 1 }, uv3 := Vector2.{ 0, 1 })
(p0: Vector3, p1: Vector3, p2: Vector3, p3: Vector3, color: Vector4, uv0 := Vector2.{ 0, 0 }, uv1 := Vector2.{ 1, 0 }, uv2 := Vector2.{ 1, 1 }, uv3 := Vector2.{ 0, 1 }, normal := Vector3.{ z = 1 })
(p0: Vector3, p1: Vector3, p2: Vector3, p3: Vector3, color: Vector4, uv0: Vector2, uv1: Vector2, uv2: Vector2, uv3: Vector2, st0: Vector2, st1: Vector2, st2: Vector2, st3: Vector2, normal: Vector3, tangent: Vector4)

Adds a rectangle from two corners, in one colour.

set_shader_for_color();
immediate_quad(10, 10, 110, 60, .{1, 1, 1, 1});

This one is only checked to compile, not run.

GL pixel read#

(source: *Texture, dest_format: Texture_Format, width: s32 = -1, height: s32 = -1, srgb := false, flip := false) -> *Pixel_Read

Starts copying the pixels of source back to the CPU without waiting for them. Poll with pixel_read_is_done, then collect them with pixel_read_end.

target := texture_create_render_target(256, 256, .RGBA8);
reader := pixel_read_begin(*target, .RGBA8, flip = true);
while !pixel_read_is_done(reader) {}
bitmap := pixel_read_end(reader);

This one is only checked to compile, not run.

Types#

:: enum

How a pixel is stored. Plain colours (RGBA8), floating point formats, integer formats, block compressed formats (BC*, ETC_*) and depth formats.

Unknown
R8
RG8
RGB8
RGBA8
RGBX8
BGRA8
B5G6R5
RGB10A2
R16
R16F
RG16F
RGBA16F
R32F
RG32F
RGBA32F
RG11B10F
RGB9E5F
R16I
RG16I
RGBA16I
R32I
RG32I
RGBA32I
BC1
BC3
BC4
BC5
BC6
BC7
ETC_RGB
ETC_RGBA
ETC_R
ETC_RG
ETC_RGB_A1
D16
D24S8
D32F
D32FS8
assert(get_image_bytes_per_texel(.RGBA8) == 4);
assert(get_image_bytes_per_texel(.R8) == 1);
↗
:: struct

Pixels in memory, before or after they are on the graphics card. stride is the bytes between rows. bitmap_alloc makes one, bitmap_load reads one from a file, and deinit frees it.

width: s32
height: s32
mipmap_count: s32
data: [] u8
stride: s32
flags: enum_flags u32 { FREE_DATA_ON_DEINIT :: 0x1; IS_SRGB :: 0x2; }
format: := Texture_Format.BGRA8
bitmap: Bitmap;
bitmap_alloc(*bitmap, 4, 2, .RGBA8);
defer deinit(*bitmap);
assert(bitmap.stride == 16 && bitmap.data.count == 32);
↗
:: struct

A font file read into memory with FreeType. load_font makes these for you.

name: string
face: FT_Face
data: [] u8
:: enum

Where the origin of a render target is. RIGHT_HANDED puts it at the bottom left with y up; LEFT_HANDED at the top left with y down.

LEFT_HANDED
RIGHT_HANDED
:: enum_flags

Extra properties of a texture, such as sRGB and Render_Target.

sRGB
Cube
Array
Volume
Buffer
Render_Target
Multisampled
No_Skip_Mipmaps
Display_Target
Texture_Filterenumsince 0.31.0Source
:: enum

How a texture is sampled when drawn larger or smaller than its size: LINEAR blends, NEAREST picks one texel.

LINEAR
NEAREST
texture: Texture;
texture_load_from_file(*texture, "sprite.png");
texture_set_filter(*texture, .NEAREST);  // crisp pixels
↗

This one is only checked to compile, not run.

:: struct

An image on the graphics card. Load one with texture_load_from_file or texture_load_from_memory, draw it after set_shader_for_images, and release it with texture_destroy.

width: s32
height: s32
depth: s32
mipmap_count: u16
array_length: u16
:: struct

What Simp knows about a window it draws into: its size in window units, the pixel_scale (device pixels per unit) and the sample count.

window: Window_Type
window_width: s32
in window coordinate units (what the application draws in)
window_height: s32
in window coordinate units (what the application draws in)
pixel_scale: float
device pixels per unit; the framebuffer is window size times this
wanted_msaa: s32
msaa: s32
backend_initted_for_this_window: := false
:: struct

A shader program. set_shader_for_color and set_shader_for_images select the built-in ones.

platform: Shader_Platform
:: enum_flags

Flags that change how text is laid out: small caps, lining figures, no kerning, left justification.

SMALLCAPS
LINING_FIGURES
NO_KERNING
LEFT_JUSTIFIED
effects: Font_Effects = .SMALLCAPS | .NO_KERNING;
assert((effects & .NO_KERNING) != 0);
↗
:: struct

One glyph of a font: its code point, where it sits in the glyph atlas, and its size and advance.

utf32: u32
glyph_index_within_font: u32
x0: s16
y0: s16
width: u32
height: u32
offset_x: s16
offset_y: s16
ascent: s16
advance: s16
page: *Font_Page
Simp_Contextstructsince 0.31.0Source
:: struct

Per-context Simp settings, read as context.simp. text_color is multiplied into the colour of text being prepared; white leaves it alone.

text_color: := Vector4.{ 1, 1, 1, 1 }
The colour recorded in each Font_Quad as text is prepared. draw_prepared_text multiplies it into the colour it is given, so white (the default) leaves that colour alone.
:: struct

A font at one size, ready to draw with. load_font returns one; it makes glyphs as text needs them and keeps them in an atlas. character_height is in device pixels, pixel_scale the number of those per drawing unit.

name: string
latin_font: *Dynamic_Font
glyph_lookup: Table(int, *Glyph_Data)
private1: FT_Face
character_range_low: int
character_range_high: int
character_height: int
in device pixels: the requested size times pixel_scale
pixel_scale: float
device pixels per drawing unit; glyphs are rasterized at this scale and drawn at 1/scale
default_line_spacing: int
max_ascender: int
max_descender: int
typical_ascender: int
typical_descender: int
em_width: int
y_offset_for_my_latin_characters: float
y_offset_for_centering: int
use_other_font_for_latin_always: bool
reversed: bool
glyph_conversion_failed: bool
glyph_index_for_unknown_character: u32
utf32_for_unknown_character: := 0xfffd
features: [..] Font_Feature
feature_smallcaps: *Font_Feature
feature_ligatures: *Font_Feature
feature_lining_numbers: *Font_Feature
temporary_glyphs: [..] *Glyph_Data
temporary_glyphs_byte_offsets: [..] int
temporary_glyphs_width_in_pixels: s64
current_quads: [..] Font_Quad
:: struct

A pending read of a render target's pixels back to the CPU. See pixel_read_begin.

dest_format: Texture_Format
dest_srgb: bool
dest_width: s32
dest_height: s32
flip: := false
platform_specific: Pixel_Read_Platform
:: struct
gl_handle: GLuint
position_loc: GLint
color_scale_loc: GLint
normal_loc: GLint
tangent_loc: GLint
uv_0_loc: GLint
uv_1_loc: GLint
diffuse_texture_loc: GLint
alpha_blend: := false

Constants#