mode-lambda: the best 2D graphics of the 90s, today!
The mode-lambda package provides a set of libraries for creating high-performance 2D graphics.
1 mode-lambda drawing model
mode-lambda renders rectangular bitmaps (called sprites) in a pixel-perfect manner on a rendering surface. The size of the rendering surface is fixed ahead-of-time when the backend is initialized. When drawn, the rendering surface is then scaled to fit the display environment’s drawing surface. mode-lambda follows an ahead-of-time, staged rendering regime whereby all color data must be "compiled", using compile-sprite-db, before any drawing can occur.
mode-lambda draws sprites on to one of 8 layers. The larger layers are on-top, with transparent pixels seeping through. mode-lambda makes no guarantees about the layering and transparency behavior of sprites within a single layer. Each layer may have an independent center point, width, scaling coefficients, rotation, wrapping behavior, and Mode-7 coefficients.
Varying the center point (cx & cy) over time creates a scrolling effect given stationary sprites on the layer. When the scrolling effect is combined with a larger or smaller frame (hw & hh) that the rendering surface, this produces a clipping effect. The scaling coefficients (mx & my) allow the entire layer to be zoomed in and out. (A scaling change over time is used in many SNES RPGs to initiate combat.) The rotation coefficient (theta) rotates the entire layer when it is drawn. The wrapping behavior (wrap-x? & wrap-y?) allows sprites that overlap with the vertical (or horizontal) edge of the screen to be partly drawn on the opposite side. Any of these changes could be implemented in software by the mode-lambda client, but the layer configuration provides a more efficient implementation.
When mode7-coeff is 0, then there is no effect.
When mode7-coeff is 1, then the horizon defines a "ceiling".
When mode7-coeff is 2, then the horizon defines a "floor". This is the most common usage of Mode-7 and is used in Mario Kart for the track and many RPGs for the world map.
When mode7-coeff is 3, then the horizon defines the middle of "cylinder".
It is easiest to understand this effect by looking at some screenshots of SNES games. (Although, mode-lambda does not support as powerful of a Mode-7 effect, because the parameters cannot be changed per Y line, but all common uses are supported.)
When mode-lambda draws a sprite, that sprite instance is associated with a palette of 16 colors. If the palette is 0, then the sprite bitmap color data is directly consulted for the color to render. Otherwise, only the green value of the sprite’s color data is used and it is used after scaling it down by 14 as an offset into the given palette.
Each sprite instance has many drawing paramters. The most important is its center point (cx & cy) and which sprite is to be drawn (spr-idx). Next, the layer (layer) and palette (pal-idx) can be specified as mentioned previously. The color of the pixels can be further influenced by a color tint. The given alpha value (a) is multiplied with the sprite/palette color, while the diffuse colors (r, g, and b) are added. Finally, each sprite may be independently scaled in the X (mx) or Y (my) direction and may be independently rotated (theta).
Each rendering pass of mode-lambda receives two trees of sprite instances. The first is called the "static" sprites and the second are the "dynamic" sprites. The static sprites are always drawn first on a given layer. Aside from this, they are treated equally, but mode-lambda will guarantee that if a tree is eq? to the tree from the last frame, the GPU’s data will not be refreshed. This guarantees that static level geometry is uploaded once, if it never changes, so mode-lambda programs should factor their graphics into static and dynamic pieces for performance.
mode-lambda is extremely memory efficient. Each sprite instance consumes 32 bytes on the CPU and 96 bytes on the GPU. If a GPU supported one gigabyte per second memory transfer, then about 180,000 sprites would be supported. Given that most GPUs support many gigabytes per second memory transfer rates, this means that performance is almost always dominated by the mode-lambda client’s preparation of the sprite trees and mode-lambda can be treated as free.
2 mode-lambda edition: amazing graphics
(require mode-lambda) | package: mode-lambda |
value
value
default-layer-config : layer-vector/c
procedure
procedure
(sprite-db? x) → boolean?
x : any/c
procedure
(sprite-attributes? x) → boolean?
x : any/c
procedure
(add-sprite! db load-spr) → void?
db : sprite-db? load-spr : (-> sprite-attributes?)
procedure
(add-sprite!/bm db n load-bm [#:palette pal]) → void?
db : sprite-db? n : symbol?
load-bm :
(-> (or/c path-string? input-port? (is-a?/c bitmap%))) pal : (or/c #f symbol?) = #f
procedure
(add-sprite!/file db n file [#:palette pal]) → void?
db : sprite-db? n : symbol? file : path-string? pal : (or/c #f symbol?) = #f
procedure
(add-sprite!/value db n val [#:palette pal]) → void?
db : sprite-db? n : symbol? val : convertible? pal : (or/c #f symbol?) = #f
procedure
(add-palette! db n cs) → void?
db : sprite-db? n : symbol? cs : (listof color?)
procedure
(add-palette!/file db n file) → void?
db : sprite-db? n : symbol? file : path-string?
procedure
db : sprite-db?
procedure
(compiled-sprite-db? x) → boolean?
x : any/c
procedure
cdb : compiled-sprite-db? path : path-string?
procedure
(load-csd path) → compiled-sprite-db?
path : path-string?
procedure
(sprite-idx cdb n) → (or/c #f exact-nonnegative-integer?)
cdb : compiled-sprite-db? n : symbol?
procedure
(palette-idx cdb n) → (or/c #f exact-nonnegative-integer?)
cdb : compiled-sprite-db? n : symbol?
procedure
(sprite-width cdb spr-idx) → exact-nonnegative-integer?
cdb : compiled-sprite-db? spr-idx : exact-nonnegative-integer?
procedure
(sprite-height cdb spr-idx) → exact-nonnegative-integer?
cdb : compiled-sprite-db? spr-idx : exact-nonnegative-integer?
procedure
(sprite cx cy spr-idx [ #:layer layer #:r r #:g g #:b b #:a a #:pal-idx pal-idx #:mx mx #:my my #:theta theta]) → sprite-data? cx : flonum? cy : flonum? spr-idx : exact-nonnegative-integer? layer : byte? = 0 r : byte? = 0 g : byte? = 0 b : byte? = 0 a : flonum? = 1.0 pal-idx : exact-nonnegative-integer? = 0 mx : flonum? = 1.0 my : flonum? = 1.0 theta : flonum? = 0.0
procedure
(layer cx cy [ #:hw hw #:hh hh #:wrap-x? wrap-x? #:wrap-y? wrap-y? #:mx mx #:my my #:theta theta #:mode7 mode7-coeff #:horizon horiz #:fov fov]) → layer-data? cx : flonum? cy : flonum? hw : flonum? = +inf.0 hh : flonum? = +inf.0 wrap-x? : boolean? = #f wrap-y? : boolean? = #f mx : flonum? = 1.0 my : flonum? = 1.0 theta : flonum? = 0.0 mode7-coeff : flonum? = 0.0 horiz : flonum? = 0.0 fov : flonum? = 1.0
3 mode-lambda color: basic color theory
(require mode-lambda/color) | package: mode-lambda |
This module defines helpers for creating color palettes for mode-lambda.
value
procedure
(color->palette base) → (listof color?)
base : color?
procedure
(color->tint base how-many) → (listof color?)
base : color? how-many : exact-nonnegative-integer?
procedure
(color->shades base how-many) → (listof color?)
base : color? how-many : exact-nonnegative-integer?
procedure
(color-wheel how-many [#:s s #:b b]) → (listof color?)
how-many : exact-nonnegative-integer? s : (real-in 0.0 1.0) = 1.0 b : (real-in 0.0 1.0) = 1.0
procedure
(complement-idxs how-many) → (listof vector?)
how-many : exact-nonnegative-integer?
procedure
(analogous-idxs how-many) → (listof vector?)
how-many : exact-nonnegative-integer?
procedure
(triadic-idxs how-many) → (listof vector?)
how-many : exact-nonnegative-integer?
procedure
(split-complementary-idxs how-many) → (listof vector?)
how-many : exact-nonnegative-integer?
procedure
(tetradic-idxs how-many) → (listof vector?)
how-many : exact-nonnegative-integer?
procedure
(square-idxs how-many) → (listof vector?)
how-many : exact-nonnegative-integer?
procedure
(polygon-idxs n how-many) → (listof vector?)
n : exact-nonnegative-integer? how-many : exact-nonnegative-integer?
4 mode-lambda text: text layout
(require mode-lambda/text) | package: mode-lambda |
This module captures some convenience functions for adding font glyphs to the sprite database and laying out horizontal text.
value
*ALL-ASCII* : (listof char?)
procedure
(load-font! db [ #:size size #:face face #:family family #:style style #:weight weight #:underlined? underlined? #:smoothing smoothing #:size-in-pixels? size-in-pixels #:hinting hinting #:alphabet alphabet]) → font? db : sprite-db? size : (real-in 0.0 1024.0) = 12 face : (or/c string? #f) = #f
family :
(or/c 'default 'decorative 'roman 'script 'swiss 'modern 'symbol 'system) = 'default style : (or/c 'normal 'italic 'slant) = 'normal weight : (or/c 'normal 'bold 'light) = 'normal underlined? : any/c = #f
smoothing :
(or/c 'default 'partly-smoothed 'smoothed 'unsmoothed) = 'default size-in-pixels : any/c = #f hinting : (or/c 'aligned 'unaligned) = 'aligned alphabet : (listof char?) = *ALL-ASCII*
procedure
(font-char-idx the-font cdb char) → exact-nonnegative-integer?
the-font : font? cdb : compiled-sprite-db? char : char?
procedure
(make-text-renderer f cdb) →
(->i ([text string?] [tx real?] [ty real?]) ([#:layer l byte?] [#:mx mx real?] [#:my my real?] [#:r r byte?] [#:g g byte?] [#:b b byte?] [#:a a (real-in 0.0 1.0)]) any/c) f : font? cdb : compiled-sprite-db?
5 mode-lambda gl: premier backend
(require mode-lambda/backend/gl) | package: mode-lambda |
This is the production backend for mode-lambda. It is pretty fast, but kind of complicated.
value
:
(->i ([cdb compiled-sprite-db?] [render-width exact-nonnegative-integer?] [render-height exact-nonnegative-integer?]) (->i ([layer-config layer-vector/c] [static-st any/c] [dynamic-st any/c]) (->i ([draw-width exact-nonnegative-integer?] [draw-height exact-nonnegative-integer?] [dc any/c]) any)))
value
value
6 mode-lambda software: discount backend
(require mode-lambda/backend/software) | |
package: mode-lambda |
This is the reference backend for mode-lambda. It is very slow, and a little complicated, but easier to test than mode-lambda/backend/gl.
value
:
(->i ([cdb compiled-sprite-db?] [render-width exact-nonnegative-integer?] [render-height exact-nonnegative-integer?]) (->i ([layer-config layer-vector/c] [static-st any/c] [dynamic-st any/c]) (->i ([draw-width exact-nonnegative-integer?] [draw-height exact-nonnegative-integer?] [dc any/c]) any)))
value