From fc006142e6c1a39fbd3b5bd3fa5fdb74eacadea8 Mon Sep 17 00:00:00 2001 From: Shengwen Cheng Date: Wed, 24 Jul 2024 13:22:39 +0800 Subject: [PATCH] Support Linux framebuffer as new backend --- Makefile | 7 +- apps/twin-apps-main.c | 164 ++++++++++++++++++++++++++++++++++++++++++ backend/fb.c | 145 +++++++++++++++++++++++++++++++++++++ backend/twin_fb.h | 30 ++++++++ 4 files changed, 345 insertions(+), 1 deletion(-) create mode 100644 apps/twin-apps-main.c create mode 100644 backend/fb.c create mode 100644 backend/twin_fb.h diff --git a/Makefile b/Makefile index 59378f6..1895774 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ # FIXME: make these entries configurable CONFIG_BACKEND_SDL := y +CONFIG_BACKEND_FB := y CONFIG_LOADER_JPEG := y CONFIG_LOADER_PNG := y @@ -88,6 +89,10 @@ libbackend.a_cflags-y += $(shell sdl2-config --cflags) TARGET_LIBS += $(shell sdl2-config --libs) endif +ifeq ($(CONFIG_BACKEND_SDL), y) +libbackend.a_files-y += backend/fb.c +endif + # Platform-specific executables ifeq ($(CONFIG_BACKEND_SDL), y) @@ -95,7 +100,7 @@ target-y += demo-sdl demo-sdl_depends-y += $(target.a-y) demo-sdl_files-y = \ - apps/twin-sdl.c + apps/twin-apps-main.c demo-sdl_includes-y := include demo-sdl_includes-y += backend diff --git a/apps/twin-apps-main.c b/apps/twin-apps-main.c new file mode 100644 index 0000000..3104505 --- /dev/null +++ b/apps/twin-apps-main.c @@ -0,0 +1,164 @@ +/* + * Twin - A Tiny Window System + * Copyright (c) 2004 Keith Packard + * All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "twin_fb.h" +#include "twin_sdl.h" + +#include "apps_calc.h" +#include "apps_clock.h" +#include "apps_demo.h" +#include "apps_hello.h" +#include "apps_line.h" +#include "apps_spline.h" + +#define WIDTH 640 +#define HEIGHT 480 + +#define ASSET_PATH "assets/" + +/** + * Load the background pixmap from storage. + * @filename: File name of a PNG background. + * + * Return a default pattern if the load of @filename fails. + */ +static twin_pixmap_t *load_background(twin_screen_t *screen, + const char *filename) +{ + twin_pixmap_t *raw_background = twin_png_to_pixmap(filename, TWIN_ARGB32); + if (!raw_background) /* Fallback to a default pattern */ + return twin_make_pattern(); + + if (screen->height == raw_background->height && + screen->width == raw_background->width) + return raw_background; + + /* Scale as needed. */ + twin_pixmap_t *scaled_background = + twin_pixmap_create(TWIN_ARGB32, screen->width, screen->height); + if (!scaled_background) { + twin_pixmap_destroy(raw_background); + return twin_make_pattern(); + } + twin_fixed_t sx, sy; + sx = twin_fixed_div(twin_int_to_fixed(raw_background->width), + twin_int_to_fixed(screen->width)); + sy = twin_fixed_div(twin_int_to_fixed(raw_background->height), + twin_int_to_fixed(screen->height)); + + twin_matrix_scale(&raw_background->transform, sx, sy); + twin_operand_t srcop = { + .source_kind = TWIN_PIXMAP, + .u.pixmap = raw_background, + }; + twin_composite(scaled_background, 0, 0, &srcop, 0, 0, NULL, 0, 0, + TWIN_SOURCE, screen->width, screen->height); + + twin_pixmap_destroy(raw_background); + + return scaled_background; +} + +static void usage(const char *execpath) +{ + fprintf(stderr, "Usage: %s -b backend [-p fb-path]\n", execpath); +} + +static void handle_options(int argc, + char **argv, + char **backend, + char **fb_path) +{ + *backend = *fb_path = NULL; + + int optidx = 0; + struct option opts[] = { + {"backend", 1, NULL, 'b'}, + {"fb-path", 1, NULL, 'p'}, + {"help", 0, NULL, 'h'}, + }; + + int c; + while ((c = getopt_long(argc, argv, "b:p:h", opts, &optidx)) != -1) { + switch (c) { + case 'b': + *backend = optarg; + break; + case 'p': + *fb_path = optarg; + break; + case 'h': + usage(argv[0]); + exit(0); + default: + break; + } + } + + if (!*backend) { + fprintf(stderr, "Must assign a backend via -b option.\n"); + usage(argv[0]); + exit(2); + } +} + +int main(int argc, char **argv) +{ + char *backend, *fb_path; + handle_options(argc, argv, &backend, &fb_path); + + twin_sdl_t *sdl; + twin_fb_t *fb; + twin_screen_t *screen; + + if (!strcmp(backend, "sdl")) { + /* Select SDL as backend */ + sdl = twin_sdl_create(WIDTH, HEIGHT); + screen = sdl->screen; + } else if (!strcmp(backend, "fb")) { + if (!fb_path) { + fprintf( + stderr, + "Must assign the path to framebuffer device via -p option.\n"); + usage(argv[0]); + exit(2); + } + + /* Select Linux framebuffer as backend */ + fb = twin_fb_create(fb_path); + screen = fb->screen; + } else { + /* Unknown backend */ + fprintf(stderr, + "Unknown backend: %s\n" + "Must be 'sdl' or 'fb'.\n", + backend); + exit(2); + } + + twin_screen_set_background(screen, + load_background(screen, ASSET_PATH "/tux.png")); + + apps_demo_start(screen, "Demo", 100, 100, 400, 400); + apps_hello_start(screen, "Hello, World", 0, 0, 200, 200); + apps_clock_start(screen, "Clock", 10, 10, 200, 200); + apps_calc_start(screen, "Calculator", 100, 100, 200, 200); + apps_line_start(screen, "Line", 0, 0, 200, 200); + apps_spline_start(screen, "Spline", 20, 20, 400, 400); + + twin_dispatch(); + return 0; +} diff --git a/backend/fb.c b/backend/fb.c new file mode 100644 index 0000000..03a7b7f --- /dev/null +++ b/backend/fb.c @@ -0,0 +1,145 @@ +/* + * Twin - A Tiny Window System + * Copyright (c) 2024 National Cheng Kung University, Taiwan + * All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "twin_fb.h" +#include "twin_private.h" + +static void _twin_fb_put_begin(twin_coord_t left, + twin_coord_t top, + twin_coord_t right, + twin_coord_t bottom, + void *closure) +{ + twin_fb_t *tx = closure; + tx->width = right - left; + tx->height = bottom - top; + tx->image_y = top; +} + +static void _twin_fb_put_span(twin_coord_t left, + twin_coord_t top, + twin_coord_t right, + twin_argb32_t *pixels, + void *closure) +{ + twin_fb_t *tx = closure; + twin_coord_t ix; + twin_coord_t iy = top; + + for (ix = left; ix < right; ix++) { + twin_argb32_t pixel = *pixels++; + + if (tx->depth == 16) + pixel = twin_argb32_to_rgb16(pixel); + + tx->pixels[iy * tx->screen->width + ix] = pixel; + } + if ((top + 1 - tx->image_y) == tx->height) { + /* Update the pixels to the framebuffer */ + memcpy(tx->fb_raw, tx->pixels, + sizeof(uint32_t) * tx->width * tx->height); + } +} + +static bool twin_fb_read_events(int maybe_unused file, + twin_file_op_t maybe_unused ops, + void *closure) +{ + /* TODO: Implement events handling for mouse, keyboard, etc. */ + return true; +} + +static bool twin_fb_work(void *closure) +{ + twin_fb_t *tx = closure; + + if (twin_screen_damaged(tx->screen)) + twin_fb_update(tx); + return true; +} + +twin_fb_t *twin_fb_create(char *fb_path) +{ + /* Allocate TWIN framebuffer object */ + twin_fb_t *tx = malloc(sizeof(twin_fb_t)); + if (!tx) { + fprintf(stderr, "Error in malloc.\n"); + exit(2); + } + + /* Open the framebuffer device */ + tx->fd = open(fb_path, O_RDWR); + if (tx->fd == -1) { + fprintf(stderr, "Error opening %s\n", fb_path); + exit(2); + } + + /* Read framebuffer information */ + struct fb_var_screeninfo info; + if (ioctl(tx->fd, FBIOGET_VSCREENINFO, &info) == -1) { + fprintf(stderr, "Error reading framebuffer information from %s\n", + fb_path); + close(tx->fd); + exit(2); + } + tx->width = info.xres; + tx->height = info.yres; + + /* Create memory mapping for accessing the framebuffer */ + tx->fb_raw = mmap(NULL, tx->width * tx->height * info.bits_per_pixel / 8, + PROT_READ | PROT_WRITE, MAP_SHARED, tx->fd, 0); + if (tx->fb_raw == MAP_FAILED) { + fprintf(stderr, "Error in mmap\n"); + close(tx->fd); + exit(2); + } + + /* Create buffer space for TWIN */ + tx->pixels = malloc(tx->width * tx->height * sizeof(uint32_t)); + memset(tx->pixels, 255, tx->width * tx->height * sizeof(uint32_t)); + + /* Create TWIN screen */ + tx->screen = twin_screen_create(tx->width, tx->height, _twin_fb_put_begin, + _twin_fb_put_span, tx); + + /* Set up framebuffer work functions */ + twin_set_file(twin_fb_read_events, 0, TWIN_READ, tx); + twin_set_work(twin_fb_work, TWIN_WORK_REDISPLAY, tx); + + return tx; +} + +void twin_fb_destroy(twin_fb_t *tx) +{ + twin_screen_destroy(tx->screen); + close(tx->fd); +} + +void twin_fb_damage(twin_fb_t *tx) +{ + int width, height; + struct fb_var_screeninfo info; + + /* Read screan width and height */ + ioctl(tx->fd, FBIOGET_VSCREENINFO, &info); + width = info.xres; + height = info.yres; + + twin_screen_damage(tx->screen, 0, 0, width, height); +} + +void twin_fb_update(twin_fb_t *tx) +{ + twin_screen_update(tx->screen); +} diff --git a/backend/twin_fb.h b/backend/twin_fb.h new file mode 100644 index 0000000..09af511 --- /dev/null +++ b/backend/twin_fb.h @@ -0,0 +1,30 @@ +/* + * Twin - A Tiny Window System + * Copyright (c) 2024 National Cheng Kung University, Taiwan + * All rights reserved. + */ + +#ifndef _TWIN_FB_H_ +#define _TWIN_FB_H_ + +#include + +typedef struct _twin_fb { + int fd; + twin_screen_t *screen; + int depth; + int *pixels; + int *fb_raw; + twin_coord_t width, height; + int image_y; +} twin_fb_t; + +twin_fb_t *twin_fb_create(char *fb_path); + +void twin_fb_destroy(twin_fb_t *tx); + +void twin_fb_damage(twin_fb_t *tx); + +void twin_fb_update(twin_fb_t *tx); + +#endif /* _TWIN_FB_H_ */