AtlatestRenderedmarkdown
Readme

sigil-graphics

2D graphics, image loading, and font rendering for Sigil.

Provides 2D rendering capabilities via sokol_gfx and sokol_gp, image loading (stb_image), and font rendering (stb_truetype).

Modules

ModulePurpose
(sigil graphics)Core 2D rendering (shapes, transforms, textures)
(sigil graphics image)CPU-side image loading
(sigil graphics font)Font loading and text rendering

System Prerequisites

  • No GL headers: the GL entry points sokol_gfx calls are loaded at run time through sigil-desktop (src/c/gl_native_loader.h, generated from the vendored sokol_gfx.h by scripts/gen-gl-loader.sh), and nothing links libGL. Building sigil-desktop needs the X11 and Wayland headers listed in manifest.scm.

Dependencies

  • sigil-stdlib
  • sigil-desktop (the window, GL context and input; GLFW. Named sigil-app through 0.9.x, pinned here as name: "sigil-desktop" at the old URL until the Codeberg repository is renamed)

Build

sigil deps install
sigil build

Render targets and custom shaders

Offscreen rendering and custom fragment shaders (since v0.10.0) build a post-processing or simulation pipeline out of three pieces:

(define rt (make-render-target 256 256))          ; 'linear sampling (default)
(define grid (make-render-target 64 64 'nearest)) ; texel-exact when drawn

(with-render-target rt
  (draw-filled-rect 0.0 0.0 256.0 256.0))         ; draws land in rt

(define fx (load-shader VERT-SOURCE FRAG-SOURCE)) ; GLSL strings; optional 3rd arg
                                                  ; 'normal (default) | 'additive | 'none
(with-shader fx
  (draw-render-target rt 0.0 0.0 256.0 256.0))    ; rt is channel 0

The fragment shader samples channel 0 as iTexChannel0_iSmpChannel0 (sokol_gp's name) and receives texUV and iColor from the vertex stage; the vertex shader is the sgp passthrough with coord at location 0 and color at location 1 (see test/test-shader). Natively the sources are #version 410; on the web config they are #version 300 es with precision qualifiers, so a package that builds for both prefixes the version line under cond-expand.

Uniforms and uniform arrays

load-shader scans the fragment source for uniform declarations of float, vec2, vec3, vec4 and mat4, scalar or array (uniform vec4 palette[8];, a literal count; a lowp/mediump/highp qualifier is fine). sampler2D is not a uniform-block entry. Up to 16 declarations (an array is one) and 512 bytes in total, both checked by load-shader (the error names the limit); a GLSL compile error is an error too, with sokol's log line before it. Entries are packed in declaration order.

(set-shader-uniform fx 'u_radius 2.5)                      ; float
(set-shader-uniform fx 'u_tint '(1.0 0.8 0.6 1.0))         ; vec4, list or vector
(set-shader-uniform fx 'palette '((1 0 0 1) (0 1 0 1) ...)) ; vec4 palette[8]: nested,
(set-shader-uniform fx 'levels #(0.0 0.25 0.5 1.0))        ; or flat, per element

The float count must match the declaration exactly; a mismatch is an error, not a partial write. Two uniforms are filled in for you on every with-shader entry: u_time (float, seconds since gfx-setup) and u_resolution (vec2: the virtual viewport, or the render target's size when the shader is bound inside with-render-target).

Textures from pixels

(define tex (make-texture-from-pixels 4 4 bytes 'nearest)) ; RGBA8 bytevector,
(update-texture tex other-bytes)                           ; top row first

bytes is exactly width * height * 4 bytes (a size mismatch is an error). The texture draws with draw-texture / draw-texture-region like one from load-texture. sokol_gfx allows one upload per texture per frame (a frame ends at end-frame), and creating the texture is that frame's upload, so make the texture one frame before the first update-texture; a second upload in a frame is refused with an error that says so.

GPU simulations by ping-pong

with-shader composes inside with-render-target: a step shader bound in a pass on target B can read target A through draw-render-target, and swapping the two each frame runs a cellular automaton with no cell state on the CPU. Load the step shader with 'none blending so it overwrites the target (alpha blending would scale data channels), and read neighbours with texelFetch(iTexChannel0_iSmpChannel0, ivec2(gl_FragCoord.xy) + offset, 0) (the sampler's filter does not apply) and wrap on u_resolution. test/test-shader-limits is the worked example, verified pixel-for-pixel on native and in the browser; its README has the commands.

Render targets on the web config work as of v0.11.3 (earlier releases refused every offscreen pass there, see the tag notes).

License

BSD-3-Clause