# Pixel buffers

The unit at [gr/buf.fs] defines the "pixel buffer" (pixbuf) struct which is used
to shove pixels around. It represents a rectangular grid of pixels of a
particular color encoding [gr/color] and depth.

This unit takes care of managing the structure, the memory of the buffer, line
addressing, coordinate selection and bounds checking. For drawing pixels, other
units such as [gr/rdwr] exist.

The buffer memory is organized as a sequence of lines with each line being a
sequence of pixels.

The origin of the buffer, that is, its first address in memory, is the bottom
left of the grid. This is unlike many other systems where the origin is top
left. This allows us to fit better with [oberon].

Because sub-8bit depths are supported, it means there can be more than a single
pixel packed in a single byte.

## Depth and endianness

Ultimately, each pixel in the buffer has an associated color of the specified
encoding. Storage modality of those pixels is determined by the buffer's depth
field. Depth, measured in BPP (bits per plane) determines how many bits of
storage is reserved for each pixel. That field's value is one of those
constants:

1BPPL 1BPPH
2BPPL 2BPPH
4BPPL 4BPPH
8BPP
16BPP
24BPP
32BPP

Depths 8BPP 16BPP and 32BPP are straightforward enough. The color is there, at a
particular address, with a particular width. You read/write it, you're happy.
The endianness of those depth is the native one.

24-bit depth is weird because there is no native access for it. It has to be
accessed byte by byte, and therefore it has an endianness: little endian. The
day we encounter hardware with a 24-bit depth organized in big endian, we'll add
24BPPBE, but we'll wait for that day to come first.

Then come the sub-8-bits depths. Fundamentally, they don't have endianness
either, but because there is more than a single pixel packed in the same number,
we have to know if the low bits are leftmost or rightmost.

Also, because we're going to do bit fiddling anyways, to maximize register usage
and minimize memory accesses, we say that all sub-8-bits depths are accessed
with 32-bit read/writes in native endian-ness.

But even then, as we stated above, we need to know pixel ordering of what we're
reading or writing. We specify this in terms of whether low bits (L) or high
bits (H) are leftmost. For example, in a 4BPPL depth, if the first 32-bits of
the line are $12345678, then the leftmost pixel of the line has the color 8. On
a 4BPPH depth, it would have the color 1.

It goes without saying that pixbufs must be created with combinations of depth
and color encoding that make sense, otherwise color values will be corrupted.
For example, RGB565 doesn't make sense under a 8BPP depth.

## Unallocated state

When initialized with "newpixbuf", the structure has "buffer" and "bufsz" set to
0. It's only when "?allocbuf" is called that they are initialized. We do this to
accomodate the possibility of having a hardware buffer setup.

## flipY

When the "flipY" flag is set, "yaddr" and "xyaddr" yield an address that
correspond to a buffer having its origin at the top left corner instead of the
bottom left one.

This flag is necessary on hardware buffers where we can't control the origin.

When you write fast pixel routines, you might not call "yaddr" each time to
target a new line. In that case, you must honor the flipY flag yourself.

### flipY as a temporary flag

This flag can also be used for buffers in regular memory with a top-left origin
such as [gr/varvara], but in those cases, it's recommended to use the flag
temporarily when needed (for example when copying parts of the buffer to
another), but otherwise keep the flag off. Otherwise, things get really
confusing really fast.

As long as you stay within the "flipped" world, it doesn't matter that the
buffer in memory is upside down. It's only when you interact with other buffers
that it begins mattering. It's then that you use the flipY flag.

For example, if you want to copy the top quarter of a 8x8 flipped buffer to the
bottom left corner of a bigger buffer (regardless of its flipY status), you'd do
something like this:

  1 srcpixbuf to flipY
  0 6 8 2 srcpixbuf 0 0 dstpixbuf movepixels
  0 srcpixbuf to flipY

When you blissfully work on your flipped rect with flipY off, it's possible that
you play around with rectangles with a top-left point of view, for example for
keeping up with [gr/damage]. In those cases, you'll want to flip that rectangle
before you send it to "movepixels". Here's another way to do the same thing as
above, but with a rectangle of a top-left perspective:

  1 srcpixbuf to flipY
  0 0 8 2 srcpixbuf fliprect 0 0 dstpixbuf movepixels
  0 srcpixbuf to flipY

## Invalidate

Whenever the buffer is changed, its "invalidate ( x y w h pixbuf -- )" method is
called once all drawing is finished. It allows for all kinds of little tricks
such as updating [gr/damage] struct or implementing proxy schemes.

## Palette resolution

A pixbuf can contain any type of color encoding, which includes PALETTE. A
pixbuf has a "palette" field to store a pointer to a palette, but it otherwise
does nothing with it. Drawing routines have to manage palette logic themselves.

## Memory and bounds management only

This unit is only about memory and bounds management. Changing actual pixels
within that buffer is left to other units such as [gr/rdwr] or [gr/blt].

## Screen variable

This unit also provides a "screen" value, which is a Pixbuf that points to the
screen that is presented to the operator. We could call it the final *raison
d'etre* of the graphical substack.

This variable is initialized to a dummy uninitialized 0x0 pixbuf. This means
that drawing to it will abort with error (or do nothing in some cases), but will
not crash the machine.

To test whether the screen has been initialized, you can't compare that value
to zero, so there's also the "hasscreen?" word to test if the screen has been
set to something else than that dummy screen.

## API

### Struct

struct Pixbuf {
  uint buffer width height encoding depth linesz bufsz palette flipY ;
  method invalidate ?allotbuf ;
}

buffer    Points to the first byte of the pixbuf
width     Width in pixels of the pixbuf
height    Height in pixels of the pixbuf
encoding  [gr/color] encoding of the pixbuf
depth     One of the depth constants
linesz    Size in bytes of a line
bufsz     Size in bytes of the allocated buffer
palette   If encoding is palette-based, pointer to palette
flipY     If nonzero, flip pixels vertically

invalidate ( width height pixbuf -- )
  Called after a buffer-mutating operation is done. At that point, target
  coordinate is still valid and can be used to determine the scope of the
  operation. Defaults to a noop.

?allotbuf ( pixbuf -- )
  Considering current width and height values, set "linesz" and check whether
  current buffer can accomodate that size. If not, allocate a new buffer and
  update "buffer" and "bufsz".

  Default implementation sets linesz as width*colorbytes, always 32-bit aligned,
  and doesn't do anything fancy in terms of allocation, just a straight buffer
  aligned to 32-bit. Some pixbufs such as [gr/varvara] override this with
  something fancier.

### Depth constants

1BPPL 1BPPH
2BPPL 2BPPH
4BPPL 4BPPH
8BPP
16BPP
24BPP
32BPP

### Values

screen    Main pixbuf of the system

### Words

depth>idx ( depth -- idx )
  Each depth constant has a unique number in the form of an index starting
  from 0 (1BPPL) and ending with 9 (32BPP). Yield that index from the specified
  depth.

depth>bpp ( depth -- bpp )
  Yields the bits per plane value of the specified depth constant. For example,
  16BPP --> 16.

newpixbuf ( depth encoding width height -- pixbuf )
  Allocate a new pixbuf with the specified field values. The rest is initialized
  to 0 and either "setbuf" or "allotbuf" must be called before usage.

setbuf ( linesz buffer pixbuf -- )
  Sets linesz, buffer and bufsz.

allotbuf ( pixbuf -- )
  Allocate a new buffer of the proper size, considering linesz and height, and
  call "setbuffer".

configurebuf ( depth encoding width height linesz buffer pixbuf -- )
  Writes all arguments to the corresponding pixbuf fields, and also set computed
  "bufsz" field.

  This is a convenience word for setting up a hardware buffer.

computelinesz ( pixbuf -- linesz )
  Yields the regular linesz that specified pixbuf should have, considering its
  depth and width.

yaddr ( y pixbuf -- a )
  Yields the address of the beginning of the line "y".
  Aborts if "y" is out of bounds.

  Note that a line in a pixbuf is always aligned to 32-bit.

yaddr, ( -- )
  Compile yaddr code expecting A=pixbuf and W=y, yielding A=pixbuf and W=addr.
  However, instead of aborting, it yields W=0 when out of bounds.
  "yaddr" of course uses this. Preserves S.

xyaddr ( x y pixbuf -- x a )
  Yields the address containing the pixel at x/y. Sub 8-bit encodings pack more
  than one pixel in a single byte. In those cases, whatever pixel manipulating
  algorithm calling this word will have to further use "x" to do bit fiddling on
  that address, which is why "x" is kept in PS.
  Aborts if "x" or "y" is out of bounds.

xyaddr, ( -- )
  Compile xyaddr code expecting A=pixbuf W=y S=x, yielding W=addr.
  However, instead of aborting, it yields W=0 when out of bounds.
  "xyaddr" of course uses this.

bounds ( x y width height pixbuf -- x y width height )
  Yields the same rectangle as input unless a part of it is outside bounds.
  That is:

  1. If x is negative, reduce width and yield x=0.
  2. If y is negative, reduce height and yield y=0.
  3. If x+width is wider than pixbuf, reduce width.
  4. If y+height is higher than pixbuf, reduce height.

centerxy ( pixbuf -- x y pixbuf )
  Yields the center point of pixbuf, slightly left/bottom if there is no exact
  center point.

  This word's signature is a bit weird because that's how this word will most be
  used: tweak X/Y coordinated right before using it in, for example, [gr/blt]
  movepixels.

centerrect ( w h pixbuf -- x y pixbuf )
  Yields the coordinates of a rectangle of specified size, in the center of
  pixbuf.

  This word's signature is a bit weird for the same reasons as alignx.

fliprect ( x y w h pixbuf -- x y w h pixbuf )
  Flip the specified rectangle, assumed to describe an area with a top-left
  origin so that it represents the same rectangle with the pixbuf flipped and in
  a bottom-left origin perspective. That is, "y" becomes "pixbuf.height-y-h".

  Implies "bounds", applied before flipping.

clear ( pb -- )
  Fill whole buffer with zeroes.

resize ( width height pb -- )
  Check if resizing the pixbuf to specifiez size fits in "bufsz". If not,
  allocate a new buffer to fit the size. This clears the buffer.

  Then, sets size to specified values, adjusting "linesz".

>screencolor ( rgb24 -- color )
  Shortcut for "screen encoding >color" [gr/color].
