Generally, the fastest way to handle graphics in Riko 4 is to rasterize everything into an image (see below) to reduce calls. However this is not always applicable as creating images is not super fast. Meaning, its not a good idea to create new images every frame. You can modify images which is a good way to make minor changes to them, however the ideal condition is to have a spritesheet with all possible forms of your graphics present.
As the Riko 4 screen is double buffered, the ideal way to draw graphics is to clear the screen, draw your graphics, then swap.
The Riko 4 has a display with a resolution of 340 pixels wide and 200 pixels tall All graphics functions have their origin at 0, 0 which is the top left pixel
The Riko 4 also uses vsync, so if you have a 144hz monitor, and your graphics card can do it, it will run at 144hz, it is important to create applications and games with this in mind, don't assume that every cycle will be 1/60th of a second long.
Colors as mapped as a 4 bit integer (0 - 15) which are (will be) reprogrammable (rgb wise) through a palette
Reprogrammable functionality interface is currently TBD
While not ideal, it is sometimes necessary to draw individual pixels, you can do this with the following interface.
gpu.drawPixel(x, y, color)
Code Example
gpu.drawPixel(1, 2, 7)
-- Draws a pixel at 1, 2, with color 7 from the color palette
Rectangles are better than writing individual pixels, and are faster than using images if your intended result is very basic, the interface is as follows
gpu.drawRectangle(x, y, w, h, color)
Code Example
gpu.drawRectangle(15, 30, 45, 50, 8)
Images are the ideal way to deal with graphics, the Image interface natively supports the RIF
(see below) graphics format; however, you can also just create a blank image and draw to it if you like, which is useful for baking textures that you generate run-time.
You can create an image datatype with the following constructor
image.createImage(imagedata)
-- Or
image.createImage(width, height)
imagedata
must be in RIF
(subject to change, support for more graphics types may come in the future)
If nothing is supplied to the function, it will error.
Code Example
local handle = io.open("myImage", "r")
local imagedata = handle:read("*a")
handle:close()
local myImage = image.createImage(imagedata)
Or
local myBlankImage = image.createImage(30, 30)
You can render an image to the screen with the following function
image:render(x, y)
However, for performance reasons, the actual texture uploaded to your computer's GPU isn't updated until you flush the image buffer with the following function.
image:flush()
Code Example
local myImage = image.createImage(32, 32)
myImage:drawRectangle(0, 0, 16, 16, 4)
myImage:drawRectangle(16, 16, 16, 16, 4)
myImage:flush()
myImage:render(50, 50)
If you have a blank image (or not), you can draw to it using any of the aforementioned functions, but replace the gpu
namespace with the image, for example:
local myImage = image.createImage(30, 30)
myImage:drawRectangle(0, 0, 30, 15, 4)
The actual Images exposed by image userdata aren't are now garbage collected, so when you're done with an image, Lua takes care of everything for you! :D
The RIF
graphics format is very simple; each color is a 4 bit integer, so we can take advantage of this by storing two pixels in each byte. There is also a header at the beginning to provide metadata about the image. The full specs are as follows:
File format:
Signature (3 bytes) -> Header (4 bytes) -> Pixel data (? bytes)
The signature is "RIF" in ascii, mapping to 82
73
and 70
as bytes
The header is comprised of 4 bytes, specifying the image's dimensions, 2 for each dimension. The first byte represents the 256s place of the x dimension and the second byte represents the 1s place of the x dimension. For example, if the headers first two bytes were 3
and 7
the width of the image would be (3 * 256) + 7
. This is a very generous amount of space and the maximum width (and height) is 256^2
, realistically speaking, an image should never have to be larger than this. The same logic applies to the height.
For the pixel data, each byte represents 2 pixels, with the second half of the last byte being padded with 0s if the total pixel count is odd. Going like this:
For two consecutive pixels having the colors 3 and 5 respectively this would be the corresponding byte:
0 0 1 1 0 1 0 1
\-----/ \-----/
3 5
The final value of the byte would be 53, in ASCII this is the number 5