Introduction to GEGL Buffers

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]


I'm back and I've changed.  I don't want to be verbose anymore.  So,
without further ado...  Wait, let me just say this: GeglBuffers are
awesome!  Now that that's out of the way, let me tell you exactly why
GeglBuffers rock.  :)


Internally, GeglBuffer data is subdivided into GeglTiles that are
always of the same size (within that same buffer).  The default tile
size is 128x64.  Tiles store image data within portions of the buffer.
 Image data within a tile is always linear, though tiles are allowed
to be in different memory locations.  Tiles do not overlap, meaning
that no two tiles share the same pixels.

GeglTiles are identified through their x, y and z coordinates.  The x
and y coordinates are the tile's horizontal and vertical positions in
the buffer, respectively.  The last coordinate, z, is the tile's
mipmap level.  Mipmaps are scaled versions of tiles.  Currently, there
are three mipmap levels; z=0 which is the original tile, z=1 & z=2
which are 50% and 25% of the original tile size, respectively.

For those with poor imagination (like me), I have prepared an
(awesome) ASCII table ('coz ASCII iz 1337) for your viewing

    <------------ 384 ------------>
 ^  +---------+---------+---------+ -+-
 |  |         |         |         |  |
 |  | (x0,y0) | (x1,y0) | (x2,y0) |  64
 |  |         |         |         |  |
 |  +---------+---------+---------+ -+-
 |  |         |         |         |  |
192 | (x0,y1) | (x1,y1) | (x2,y1) |  64
 |  |         |         |         |  |
 |  +---------+---------+---------+ -+-
 |  |         |         |         |  |
 |  | (x0,y2) | (x1,y2) | (x2,y2) |  64
 |  |         |         |         |  |
 v  +---------+---------+---------+ -+-
    |-- 128 --|-- 128 --|-- 128 --|

In the example above, we have a 384x192 buffer.  Internally, the image
is subdivided into nine tiles, 128x64 each.  The pixels belonging to
the top left-most rectangle is stored in tile (x0,y0), the top-most
middle rectangle in tile (x1,y0), the top right-most rectangle in tile
(x2,y0), the middle left-most rectangle in tile (x0,y1) and so on.
Note that the tiles aren't necessarily stored in contiguous (linear)
memory.  In fact, some tiles may not even be in main memory at all[1]!

A buffer whose width is not a multiple of its tile width will pad its
right-most tiles.  Similarly, a buffer whose height is not a multiple
of its tile height will pad its bottom-most tiles.  A buffer whose
width & height are both not multiples of its tile width & height will
introduce padding to its right-most and bottom-most tiles (including,
of course, the bottom right-most tile) as illustrated by another
(equally awesome) ASCII table below:

|        |        |  |00000|
|        |        |  |00000|
|        |        |  |00000|
|        |        |  |00000|
|        |        |  |00000|
|        |        |  |00000|
|        |        |  |00000|

Tile portions marked with 0s are in the abyss (outside the image) and
are padded[2].


The GeglBuffer class is part of a large family of classes called
TileSources.  A TileSource is an object that provides tile commands.
The following commands are exposed by all TileSources:

GEGL_TILE_IDLE - used internally to run tile idle work
GEGL_TILE_SET - set the tile at a particular xyz location
GEGL_TILE_GET - get the tile at a particular xyz location
GEGL_TILE_IS_CACHED - query if a tile is cached
GEGL_TILE_EXIST - query if a tile exists
GEGL_TILE_VOID - causes all the references to a tile to be removed
GEGL_TILE_FLUSH - flush all cached tiles to the backing store
GEGL_TILE_REFETCH - used internally to refresh all data relating to
the coordinates

Note that not all TileSources implement all of the commands.  Each
TileSource subclass implements a subset of the commands.  Each of this
TileSources work hand-in-hand to provide all the needed commands by
the GeglBuffer.  More about this below.

Two classes directly inherit from the TileSource; the TileHandler and
the TileBackend classes.  The latter implements full storage for the
tiles.  As its name implies, the TileBackend is where data is
ultimately read from or written to (at least in the whole duration of
the program).  As such, a TileBackend implements the tile get and set
commands (among others, but I'm not sure).  Currently, there are three
TileBackend implementations; a RAM, a file and a tiledir backend.

TileHandlers, on the other hand, has this idea of a source.  When a
specific TileHandler doesn't implement a command, the TileHandler
calls the same command from its source.  It can also call the source's
command when it needs data to process.  To elaborate, let's look at
the multitude of TileHandler implementations.  Currently, there are
five direct TileHandler subclasses; TileHandlerCache, TileHandlerZoom,
TileHandlerEmpty, TileHandlerChain and the GeglBuffer (the only one in
the family with super-cow powers :).

The TileHandlerCache implements (almost?) all commands.  This
TileHandlerCache caches tiles as they are retrieved from its source.
Cached tiles are also written-back to the source through the
TileHandlerCache's idle command.

The TileHandlerZoom implements the get tile command to create a mipmap
when the mipmapped tile from its source is NULL.  This TileHandlerZoom
kicks into play only when z > 0.

The TileHandlerEmpty implements the get tile command to create a new
tile when the tile from its source is NULL.  It does this by creating
a tile that shares its data with the TileHandlerEmpty's pre-created
empty tile[3].

The TileHandlerChain doesn't implement any of the commands.  It,
however, delegates all of its commands to its source.  What the
TileHandlerChain really does is accept a list of TileHandlers and
chain them.  The TileHandlerChain sets these TileHandlers so that the
first in the list becomes the TileHandlerChain's source and the next
(i + 1)th TileHandler becomes ith's source (where i > 0 & i is
zero-based).  This lets a command pass through the chain looking for a
TileHandler that will handle it.

There is a special kind of TileHandlerChain called a TileStorage.  A
TileStorage is a TileHandlerChain that creates it's own set of
TileHandlers to chain.  Furthermore, the TileStorage attaches a
TileBackend to the last element of the chain.  Currently, the
TileStorage chains TileHandlers like so:


In the diagram above, the arrow direction points from the TileHandler
to its source.  This means that a command to the TileStorage will pass
through the chain (storage->empty->zoom->cache->backend) until either
it is handled or it reaches the backend.  For get tile, for example,
the command will just go through all the TileHandlers until the
TileHandlerCache.  By which point, it will either return the tile from
the cache or chain the command to the TileBackend depending on whether
the tile is cached or not, respectively.

Consider the example in which the buffer is newly created so that
there is no pixel data in the backend itself.  A get tile command will
go through the chain until it gets to the backend (the cache doesn't
have data, of course, so it's also skipped).  The moment the command
returns unsuccessfully, the TileHandlerEmpty will catch the request
and create a new shared empty tile to be returned.  The same also
occurs for the TileHandlerZoom.  Only that it returns mipmaps instead
of empty tiles.

The last TileHandler is the GeglBuffer.  A GeglBuffer is a TileHandler
that sets a TileStorage as it's source.  GeglBuffers provide
facilities to query and set rectangle pixel values.  You can create a
GeglBuffer that uses another GeglBuffer as its source (remember that
GeglBuffers are still TileSources).  It let's you convert from one
color to another through Babl.  It's lets you store data in a linear
memory, RAM or swap.  It can feed your dog, clean the house and kiss
your girlfriend for you.  As I've said, GeglBuffers have super-cow
powers!  But only because of the powerful architecture it sits on top

Ain't GeglBuffers awesome?

I know that I promised a new architecture.  But, really, that new one
is still up in the air as I'm still sorting out the details.  Hope
this article will be of help to anyone.

Kind regards,

P.S.  Apologies if the humor is cheesy.  Half of being a programmer is
having a rotten sense of humor.  It's from the same faculty as
playfulness, I think.  :)

[1] Now you know what a tiled sparse buffer is.  (Well, half of the
definition, actually.  GEGL would also delay allocation of image data
until a tile is actually used to store pixels.  That is, a newly
created tile would not occupy memory until they are written to.  Thus,
the other half of the 'sparse' definition.)

[2] With zeros, I think.

[3] A shared tile is a tile whose data is shared by other tile
instances.  Sharing tile data is a smart way of creating copy-on-write
tiles that doesn't occupy significant memory during creation.  Prior
to the writing of pixel data, a shared copy-on-write tile is unshared
and proper memory is allocated for the tile.
Gegl-developer mailing list

[Video For Linux]     [Photo]     [Yosemite News]    [Yosemite Photos]    [gtk]     [GIMP Users]     [KDE]     [Scanner]     [Gimp's Home]     [Gimp on Windows]     [Steve's Art]     [Webcams]