A small framebuffer-console demo program (32 BPP only). Run it while waiting for long compile-jobs to finish (originally for Linux).
Compile and run:
$ cc -Wall -Wextra -O2 -march=native -pipe -o rect rect.c
$ ./rect
No root
privs. needed. Untested on big-endian machines.
rect.c
/**
* NetBSD wsdisplay(4) framebuffer demo.
*
* Ctrl-C to exit program.
*/
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <err.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <dev/wscons/wsconsio.h>
/**
* Declarations
*/
typedef struct framebuffer {
int fd;
uint32_t xres;
uint32_t yres;
uint32_t bpp; /* bytes per pixel */
uint32_t stride;
uint32_t roff;
uint32_t goff;
uint32_t boff;
u_int omode;
size_t len;
size_t mapsz;
void* fb;
} Framebuffer;
typedef struct point {
int32_t x, y;
} Point;
typedef struct line {
Point min, max;
} Line;
typedef struct rectangle {
Point min, max;
} Rectangle;
typedef struct colour {
uint8_t red, green, blue;
} Colour;
static Framebuffer* fb_init(char* fbdev);
static void fb_done(Framebuffer* fb);
static void fb_clear(Framebuffer* fb);
static void fb_set(Framebuffer* fb, Colour c);
static void fb_rect(Framebuffer* fb, Rectangle r, Colour c);
static void fb_box(Framebuffer* fb, Rectangle r, int th, Colour c);
static void fb_point(Framebuffer* fb, Point p, Colour c);
static void fb_hline(Framebuffer* fb, Line l, int th, Colour c);
static void fb_vline(Framebuffer* fb, Line l, int th, Colour c);
static void make_boxes(Framebuffer* fb);
static Rectangle mk_rnd_rect(int maxx, int maxy, int szx, int szy);
static Colour mk_colour(int r, int g, int b);
static Colour mk_rnd_colour(int maxc);
typedef void (*sighandler_t)(int);
static sighandler_t xsignal(int signo, sighandler_t handler);
static void sig_hand(int signo);
static void pr_info(char* fbdev);
static int do_opts(int argc, char* argv[]);
static void usage(void);
/**
* Globals
*/
static struct options {
char* devname; /* FB device to open */
bool info; /* print info. and exit if true */
} opts;
static bool do_exit = false; /* program will exit if true */
static Colour White = { 0xFF, 0xFF, 0xFF };
static Colour Black = { 0, 0, 0 };
static Colour Red = { 0xFF, 0, 0 };
static Colour Green = { 0, 0xFF, 0 };
static Colour Blue = { 0, 0, 0xFF };
/**
* M A I N
*/
int
main(int argc, char* argv[])
{
Framebuffer* fb;
int rc = EXIT_FAILURE;
if (getenv("DISPLAY") != NULL)
errx(rc, "run this on a framebuffer-console--ie. not in X");
do_opts(argc, argv);
if (opts.info) {
pr_info(opts.devname);
exit(EXIT_SUCCESS);
}
printf("Enter to run. Ctrl-C to quit.\n");
printf("(and, don't switch VTs)\n\n");
getchar();
fb = fb_init(opts.devname);
if (fb == NULL)
err(rc, "error initializing framebuffer: %s", opts.devname);
xsignal(SIGHUP, sig_hand);
xsignal(SIGINT, sig_hand);
xsignal(SIGQUIT, sig_hand);
xsignal(SIGTERM, sig_hand);
xsignal(SIGUSR1, sig_hand);
fb_clear(fb);
fb_set(fb, White);
make_boxes(fb);
fb_clear(fb);
fb_done(fb);
return EXIT_SUCCESS;
}
static void
make_boxes(Framebuffer* fb)
{
float max_fill;
float filled = 0;
/* Fill 80% of the screen before wiping clean */
#if 0
/*
* TODO: determine non-overlapping areas (ie. account for `damage')
* first before using this:
*/
max_fill = (fb->xres * fb->yres) * ((float)80 / (float)100);
#endif
/* until then, we use this which looks better */
max_fill = fb->len * ((float)80 / (float)100);
while (! do_exit) {
Rectangle r = mk_rnd_rect(fb->xres, fb->yres, fb->xres/4, fb->yres/4);
Colour c = mk_rnd_colour(256);
/* update filled area */
filled += (r.max.x - r.min.x) * (r.max.y - r.min.y);
/* too many boxes--clean slate */
if (filled > max_fill) {
fb_set(fb, White);
filled = 0;
continue;
}
/* draw filled rectangle */
fb_rect(fb, r, c);
/*
* draw a hollow rectangle (border) _over_ prev. rectangle
* (rather than _around_ it).
*/
fb_box(fb, r, 3, Black);
usleep(25000); /* don't hog CPU */
}
}
static Rectangle
mk_rnd_rect(int xres, int yres, int szx, int szy)
{
enum { MIN = 20 }; /* minimum rect. side-length */
Rectangle r;
r.min = (Point){ random() % xres, random() % yres };
r.max = (Point){ r.min.x + MIN + random() % szx,
r.min.y + MIN + random() % szy };
return r;
}
static Colour
mk_rnd_colour(int maxc)
{
return mk_colour(random()%maxc, random()%maxc, random()%maxc);
}
static Colour
mk_colour(int r, int g, int b)
{
return (Colour){ r, g, b };
}
static Framebuffer*
fb_init(char* fbdev)
{
struct wsdisplayio_fbinfo fbi;
u_int omode, mode;
size_t pagemask;
Framebuffer* fb = NULL;
int fd = open(fbdev, O_RDWR);
if (fd == -1) {
warn("%s: open failed", fbdev);
return NULL;
}
if (ioctl(fd, WSDISPLAYIO_GET_FBINFO, &fbi) == -1) {
warn("ioctl(WSDISPLAYIO_GET_FBINFO) failed");
return NULL;
}
if (fbi.fbi_bitsperpixel != 32) {
warnx("Framebuffer depth (%d) != 32", fbi.fbi_bitsperpixel);
return NULL;
}
if (fbi.fbi_pixeltype != WSFB_RGB) {
warnx("Pixeltype (%d) not RGB", fbi.fbi_pixeltype);
return NULL;
}
if (ioctl(fd, WSDISPLAYIO_GMODE, &omode) == -1) {
warn("ioctl(WSDISPLAYIO_GMODE) failed");
return NULL;
}
mode = WSDISPLAYIO_MODE_DUMBFB;
if (ioctl(fd, WSDISPLAYIO_SMODE, &mode) == -1) {
warn("can't switch to dumb FB mode");
return NULL;
}
if ((fb = malloc(sizeof (Framebuffer))) == NULL) {
warn("error allocating FB struct");
return NULL;
}
fb->fd = fd;
fb->fb = NULL;
fb->xres = fbi.fbi_width;
fb->yres = fbi.fbi_height;
fb->bpp = fbi.fbi_bitsperpixel / 8; /* bytes per pixel */
fb->stride = fbi.fbi_stride; /* bytes, not pixels */
fb->roff = fbi.fbi_subtype.fbi_rgbmasks.red_offset;
fb->goff = fbi.fbi_subtype.fbi_rgbmasks.green_offset;
fb->boff = fbi.fbi_subtype.fbi_rgbmasks.blue_offset;
fb->omode = omode; /* save orig. mode */
fb->len = fbi.fbi_fbsize; /* should be == stride * yres */
pagemask = getpagesize() - 1;
fb->mapsz = (fb->len + pagemask) & ~pagemask;
fb->fb = mmap(0, fb->mapsz, PROT_READ|PROT_WRITE, MAP_SHARED, fd, fbi.fbi_fboffset);
if ((void* )fb->fb == MAP_FAILED) {
warn("error mapping framebuffer");
free(fb);
return NULL;
}
return fb;
}
static void
fb_point(Framebuffer* fb, Point p, Colour c)
{
if (p.x < 0 || p.x >= fb->xres || p.y < 0 || p.y >= fb->yres)
return;
uint32_t i = p.y * fb->xres + p.x;
uint32_t* buf = (uint32_t* )fb->fb + i;
*buf = 0 | (c.red << fb->roff) | (c.green << fb->goff) | (c.blue << fb->boff);
}
static void
fb_rect(Framebuffer* fb, Rectangle r, Colour c)
{
for (int y = r.min.y; y <= r.max.y; y++)
for (int x = r.min.x; x <= r.max.x; x++)
fb_point(fb, (Point){ x, y }, c);
}
static void
fb_box(Framebuffer* fb, Rectangle r, int th, Colour c)
{
Line l;
if (th <= 0)
return;
l.min = r.min;
l.max = (Point){ r.max.x, r.min.y };
fb_hline(fb, l, th, c);
l.min = r.min;
l.max = (Point){ r.min.x, r.max.y };
fb_vline(fb, l, th, c);
l.min = (Point){ r.min.x, r.max.y - (th - 1) };
l.max = (Point){ r.max.x, r.max.y - (th - 1) };
fb_hline(fb, l, th, c);
l.min = (Point){ r.max.x - (th - 1), r.min.y };
l.max = (Point){ r.max.x - (th - 1), r.max.y };
fb_vline(fb, l, th, c);
}
static void
fb_hline(Framebuffer* fb, Line l, int th, Colour c)
{
int i, x, y;
for (x = l.min.x, y = l.min.y; x <= l.max.x; x++)
for (i = 0; i < th; i++)
fb_point(fb, (Point){ x, y + i }, c);
}
static void
fb_vline(Framebuffer* fb, Line l, int th, Colour c)
{
int i, x, y;
for (x = l.min.x, y = l.min.y; y <= l.max.y; y++)
for (i = 0; i < th; i++)
fb_point(fb, (Point){ x + i, y }, c);
}
static void
fb_set(Framebuffer* fb, Colour c)
{
Rectangle r;
r.min = (Point){ 0, 0 };
r.max = (Point){ fb->xres - 1, fb->yres - 1 };
fb_rect(fb, r, c);
}
static void
fb_clear(Framebuffer* fb)
{
memset(fb->fb, 0, fb->len);
}
static void
fb_done(Framebuffer* fb)
{
if (munmap(fb->fb, fb->mapsz) == -1)
warn("munmap() failed");
if (ioctl(fb->fd, WSDISPLAYIO_SMODE, &fb->omode) == -1)
warn("can't switch to orig. mode");
close(fb->fd);
free(fb);
}
/**
* Implement reliable signal() using POSIX sigaction().
*/
static sighandler_t
xsignal(int signo, sighandler_t handler)
{
struct sigaction sa, osa;
sa.sa_handler = handler;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
if (sigaction(signo, &sa, &osa) == -1)
return SIG_ERR;
return osa.sa_handler;
}
static void
sig_hand(int signo)
{
switch (signo) {
case SIGHUP:
case SIGINT:
case SIGQUIT:
case SIGTERM:
do_exit = true; /* program should exit */
break;
}
}
static void
pr_info(char* fbdev)
{
struct wsdisplay_fbinfo fbi;
int rc = EXIT_FAILURE;
u_int stride;
int fd = open(fbdev, O_RDWR);
if (fd == -1)
err(rc, "%s: open failed", fbdev);
if (ioctl(fd, WSDISPLAYIO_GINFO, &fbi) == -1)
err(rc, "ioctl(WSDISPLAYIO_GINFO) failed");
if (ioctl(fd, WSDISPLAYIO_LINEBYTES, &stride) == -1)
err(rc, "ioctl(WSDISPLAYIO_LINEBYTES) failed");
close(fd);
printf( "Width\t= %d pixels\n"
"Height\t= %d pixels\n"
"BPP\t= %d bits\n"
"Stride\t= %d bytes\n",
fbi.width, fbi.height, fbi.depth, stride );
}
/**
* Process program options.
*/
static int
do_opts(int argc, char* argv[])
{
int opt;
/* defaults */
opts.info = false;
opts.devname = ttyname(STDIN_FILENO);
if (opts.devname == NULL)
opts.devname = "/dev/ttyE0";
while ((opt = getopt(argc, argv, "d:i")) != -1) {
switch (opt) {
case 'd':
opts.devname = optarg;
break;
case 'i':
opts.info = true;
break;
default:
usage();
exit(EXIT_FAILURE);
}
}
return optind;
}
/**
* Print usage information.
*/
static void
usage(void)
{
printf(
"Usage: %s [-d device] [-i]\n"
"Draw randomly coloured boxes on a wsdisplay(4) framebuffer.\n"
"Use Ctrl-C to stop.\n"
"\n"
" -d device Open `device' instead of the default (%s)\n"
" -i Print framebuffer info. then exit\n",
getprogname(), opts.devname
);
}