Hey, 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. :) GEGL TILES, WHAT THEY ARE AND HOW THEY ARE STORED 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 entertainment: <------------ 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| +--------+--------+--+00000| |00000000|00000000|00000000| +--------+--------+--------+ Tile portions marked with 0s are in the abyss (outside the image) and are padded[2]. GEGL BUFFER AWESOMENESS 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: TileStorage | v TileHandlerEmpty | v TileHandlerZoom | v TileHandlerCache | v (TileBackend) 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 of. 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, Daerd 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 Gegl-developer@xxxxxxxxxxxxxxxxxxxxxx https://lists.XCF.Berkeley.EDU/mailman/listinfo/gegl-developer