/* * Copyright (c) 2006-2023, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2023-02-25 GuEe-GUI the first version */ #include #include #include "psf.h" #include "render.h" struct render { struct rt_device *fbdev; struct rt_device_graphic_info info; struct fb_var_screeninfo var; struct fb_fix_screeninfo fix; struct psf_font psf; rt_uint32_t xlate; rt_uint32_t font_line_length; rt_uint32_t buffer_idx; rt_size_t buffer_size; rt_size_t line_size; rt_size_t screen_size; rt_size_t font_size; rt_uint8_t color_size; rt_uint8_t *cursor, *cursor_backend; struct render_point position, start_point, end_point; rt_uint32_t foreground, background; rt_uint32_t raw_foreground, raw_background; rt_uint32_t red_off, green_off, blue_off, alpha_off; rt_uint32_t red_mask, green_mask, blue_mask, alpha_mask; void *hook_ptr; void (*set_pixel)(void *fb, rt_uint32_t color); rt_uint32_t (*get_pixel)(void *fb); void *(*last_buffer)(void); void *(*next_buffer)(void); void (*move_buffer)(void *dst, void *src, rt_size_t size); void (*pan_display)(void); }; static struct render _render = {}; rt_err_t render_load_fbdev(struct rt_device *fbdev) { rt_err_t err = RT_EOK; struct render *rd = &_render; if ((err = rt_device_open(fbdev, 0))) { return err; } rd->fbdev = fbdev; err |= rt_device_control(fbdev, FBIOGET_VSCREENINFO, &rd->var); err |= rt_device_control(fbdev, FBIOGET_FSCREENINFO, &rd->fix); err |= rt_device_control(fbdev, RTGRAPHIC_CTRL_GET_INFO, &rd->info); rt_device_close(fbdev); return err; } static rt_uint32_t parse_color(struct render_color *color) { rt_uint32_t color_value; struct render *rd = &_render; color_value = ((color->red & rd->red_mask) << rd->red_off) | ((color->green & rd->green_mask) << rd->green_off) | ((color->blue & rd->blue_mask) << rd->blue_off); if (rd->var.transp.length) { color_value |= (color->alpha & rd->alpha_mask) << rd->alpha_off; } return color_value; } static void set_pixel16(void *fb, rt_uint32_t color) { rt_memcpy(fb, &color, 2); } static rt_uint32_t get_pixel16(void *fb) { rt_uint16_t color; rt_memcpy(&color, fb, 2); return color; } static void set_pixel24(void *fb, rt_uint32_t color) { rt_memcpy(fb, &color, 3); } static rt_uint32_t get_pixel24(void *fb) { rt_uint32_t color; rt_memcpy(&color, fb, 3); return color; } static void set_pixel32(void *fb, rt_uint32_t color) { *(rt_uint32_t *)fb = color; } static rt_uint32_t get_pixel32(void *fb) { return *(rt_uint32_t *)fb; } static void *last_buffer_single(void) { struct render *rd = &_render; return (void *)rd->info.framebuffer; } static void *fb_next_buffer_dummy(void) { return last_buffer_single(); } static void fb_move_buffer_single(void *dst, void *src, rt_size_t size) { rt_memmove(dst, src, size); } static void fb_pan_display_dummy(void) { } static void *last_buffer_multi(void) { struct render *rd = &_render; return (void *)rd->info.framebuffer + rd->buffer_idx * rd->line_size; } static void *fb_next_buffer(void) { struct render *rd = &_render; if (rd->buffer_idx < rd->buffer_size) { ++rd->buffer_idx; } else { rd->buffer_idx = 0; } return last_buffer_multi(); } static void fb_move_buffer_multi(void *dst, void *src, rt_size_t size) { struct render *rd = &_render; if (rd->buffer_idx == 0) { fb_move_buffer_single(dst, src, size); } } static void fb_pan_display(void) { struct render *rd = &_render; rd->var.yoffset = rd->buffer_idx * rd->psf.height; rt_device_control(rd->fbdev, FBIOPAN_DISPLAY, &rd->var); } static void color_hook_dummy(void *, rt_uint32_t *); rt_err_t render_load_font(const char *psf_data, rt_size_t size, struct render_color *foreground, struct render_color *background, struct render_point *out_start_point, struct render_point *out_end_point) { rt_err_t err; rt_uint8_t *font_data, *cursor, *cursor_backend; rt_uint32_t foreground_color, background_color; struct psf_font new_psf; struct render *rd = &_render; if ((err = psf_initialize(psf_data, &new_psf))) { return err; } if (!new_psf.count) { return -RT_EEMPTY; } /* Font + Cursor + Cursor backend */ rd->color_size = rd->var.bits_per_pixel / 8; font_data = rt_malloc_align(rd->color_size * new_psf.glyph * (new_psf.count + 2), sizeof(rt_ubase_t)); if (!font_data) { return -RT_ENOMEM; } cursor = font_data + rd->color_size * new_psf.glyph * new_psf.count; cursor_backend = cursor + rd->color_size * new_psf.glyph * new_psf.count; rd->red_off = rd->var.red.offset; rd->red_mask = RT_GENMASK(rd->var.red.length, 0); rd->green_off = rd->var.green.offset; rd->green_mask = RT_GENMASK(rd->var.green.length, 0); rd->blue_off = rd->var.blue.offset; rd->blue_mask = RT_GENMASK(rd->var.blue.length, 0); rd->alpha_off = rd->var.transp.offset; rd->alpha_mask = RT_GENMASK(rd->var.transp.length, 0); foreground_color = parse_color(foreground); background_color = parse_color(background); if ((err = psf_parse(&new_psf, font_data, cursor_backend, rd->color_size, foreground_color, background_color))) { rt_free(font_data); return err; } if (rd->psf.font_data) { rt_free((void *)rd->psf.font_data); } rd->foreground = rd->raw_foreground = foreground_color; rd->background = rd->raw_background = background_color; rd->hook_ptr = &color_hook_dummy; rt_memcpy(&rd->psf, &new_psf, sizeof(rd->psf)); rd->font_line_length = rd->psf.width * rd->color_size; rd->xlate = rd->fix.line_length - rd->font_line_length; rd->line_size = rd->psf.height * rd->fix.line_length; rd->screen_size = rd->var.xres * rd->var.yres * rd->color_size; rd->font_size = rd->psf.glyph * rd->color_size; rd->buffer_idx = 0; rd->buffer_size = (rd->fix.smem_len - rd->screen_size) / rd->line_size; if (rd->buffer_size) { rd->var.yres_virtual = (rd->fix.smem_len / rd->screen_size) * rd->var.yres; if (rt_device_control(rd->fbdev, FBIOPUT_VSCREENINFO, &rd->var)) { /* Enable double buffer fail */ rd->buffer_size = 0; } } if (rd->buffer_size) { rd->last_buffer = last_buffer_multi; rd->next_buffer = fb_next_buffer; rd->move_buffer = fb_move_buffer_single; rd->pan_display = fb_pan_display; } else { rd->last_buffer = last_buffer_single; rd->next_buffer = fb_next_buffer_dummy; rd->move_buffer = fb_move_buffer_multi; rd->pan_display = fb_pan_display_dummy; } rd->cursor = cursor; rd->cursor_backend = cursor_backend; rd->start_point.row = rd->var.yoffset / rd->psf.height; rd->start_point.col = rd->var.xoffset / rd->psf.width; rd->end_point.row = rd->var.yres / rd->psf.height - 1; rd->end_point.col = rd->var.xres / rd->psf.width - 1; if (out_start_point) { rt_memcpy(out_start_point, &rd->start_point, sizeof(*out_start_point)); } if (out_end_point) { rt_memcpy(out_end_point, &rd->end_point, sizeof(*out_end_point)); } switch (rd->var.bits_per_pixel) { case 32: rd->set_pixel = set_pixel32; rd->get_pixel = get_pixel32; break; case 24: rd->set_pixel = set_pixel24; rd->get_pixel = get_pixel24; break; case 16: rd->set_pixel = set_pixel16; rd->get_pixel = get_pixel16; break; default: break; } return RT_EOK; } void render_clear_display(void) { void *fb, *fb_end; rt_uint32_t color; rt_size_t color_size; struct render *rd = &_render; typeof(rd->set_pixel) set_pixel_handler = rd->set_pixel; fb = rd->next_buffer(); fb_end = fb + rd->screen_size; color = rd->background; color_size = rd->color_size; while (fb < fb_end) { set_pixel_handler(fb, color); fb += color_size; } rd->pan_display(); render_move_cursor(RT_NULL); } static void roll_display(void) { rt_uint32_t color; rt_size_t screen_size, flush_size, color_size; void *old_fb, *fb, *fb_end; struct render *rd = &_render; typeof(rd->set_pixel) set_pixel_handler = rd->set_pixel; color = rd->background; color_size = rd->color_size; screen_size = rd->screen_size; flush_size = screen_size - rd->line_size; old_fb = rd->last_buffer(); fb = rd->next_buffer(); fb_end = fb + screen_size; rd->move_buffer(fb, old_fb + rd->line_size, flush_size); /* The last line */ fb += flush_size; while (fb < fb_end) { set_pixel_handler(fb, color); fb += color_size; } rd->pan_display(); rt_device_control(rd->fbdev, FBIO_WAITFORVSYNC, RT_NULL); } static void color_hook_dummy(void *fb, rt_uint32_t *out_color) { } static void color_hook_transform(void *fb, rt_uint32_t *out_color) { rt_uint32_t color; struct render *rd = &_render; rt_memcpy(&color, out_color, rd->color_size); if (color == rd->raw_foreground) { *out_color = rd->foreground; } else if (color == rd->raw_background) { *out_color = rd->background; } } void render_set_foreground(struct render_color *foreground) { struct render *rd = &_render; rd->foreground = parse_color(foreground); if (rd->foreground != rd->raw_foreground) { rd->hook_ptr = color_hook_transform; } else { rd->hook_ptr = color_hook_dummy; } } void render_set_background(struct render_color *background) { struct render *rd = &_render; rd->background = parse_color(background); if (rd->foreground != rd->raw_foreground) { rd->hook_ptr = color_hook_transform; } else { rd->hook_ptr = color_hook_dummy; } } static void color_hook_invert(void *fb, rt_uint32_t *out_color) { struct render *rd = &_render; *out_color ^= rd->get_pixel(fb) & ~(rd->alpha_mask << rd->alpha_off); } static void draw_block(void *block, void (*color_hook)(void *fb, rt_uint32_t *out_color)) { void *fb; rt_size_t color_size; int font_width, font_height, xlate; struct render *rd = &_render; typeof(rd->set_pixel) set_pixel_handler = rd->set_pixel; color_size = rd->color_size; font_width = rd->psf.width; font_height = rd->psf.height; xlate = rd->xlate; fb = rd->last_buffer(); fb += rd->position.col * rd->font_line_length + rd->position.row * rd->line_size; for (int y = 0; y < font_height; ++y) { for (int x = 0; x < font_width; ++x) { rt_uint32_t color = *(rt_uint32_t *)block; color_hook(fb, &color); set_pixel_handler(fb, color); fb += color_size; block += color_size; } fb += xlate; } } static void cursor_leave(void) { struct render *rd = &_render; draw_block(rd->cursor, &color_hook_invert); } static void cursor_update(void) { struct render *rd = &_render; draw_block(rd->cursor, &color_hook_invert); rt_device_control(rd->fbdev, FBIO_WAITFORVSYNC, RT_NULL); } void render_select_cursor(enum cursor shape) { void *cursor; rt_size_t color_size; int font_width, font_height; rt_uint32_t foreground, background; struct render *rd = &_render; typeof(rd->set_pixel) set_pixel_handler = rd->set_pixel; cursor = rd->cursor; font_width = rd->psf.width; font_height = rd->psf.height; foreground = rd->foreground; background = rd->background; color_size = rd->color_size; switch (shape) { case CURSOR_HLINE: for (int y = 0; y < font_height; ++y) { for (int x = 0; x < font_width; ++x) { if (y + 1 < font_height) { set_pixel_handler(cursor, background); } else { set_pixel_handler(cursor, foreground); } cursor += color_size; } } break; case CURSOR_VLINE: for (int i = 0; i < font_height; ++i) { set_pixel_handler(cursor, foreground); cursor += color_size; for (int x = 1; x < font_width; ++x) { set_pixel_handler(cursor, background); cursor += color_size; } } break; case CURSOR_BLOCK: for (int y = 0; y < font_height; ++y) { for (int x = 0; x < font_width; ++x) { set_pixel_handler(cursor, foreground); cursor += color_size; } } break; default: return; } cursor_update(); } void render_move_cursor(struct render_point *position) { struct render *rd = &_render; if (position) { cursor_leave(); if (position->row > rd->end_point.row || position->col > rd->end_point.col) { return; } rt_memcpy(&rd->position, position, sizeof(rd->position)); } else { rt_memset(&rd->position, 0, sizeof(rd->position)); } cursor_update(); } void render_reset_cursor(struct render_point *out_position) { struct render *rd = &_render; cursor_leave(); rd->position.col = rd->start_point.col; cursor_update(); render_current_cursor(out_position); } void render_return_cursor(struct render_point *out_position) { struct render *rd = &_render; cursor_leave(); rd->position.col = rd->start_point.col; if (rd->position.row >= rd->end_point.row) { roll_display(); } else { ++rd->position.row; } cursor_update(); render_current_cursor(out_position); } void render_current_cursor(struct render_point *out_position) { struct render *rd = &_render; if (out_position) { rt_memcpy(out_position, &rd->position, sizeof(rd->position)); } } void render_put_char(char ch) { struct render *rd = &_render; draw_block((void *)rd->psf.font_data + ch * rd->font_size, rd->hook_ptr); ++rd->position.col; if (rd->position.col > rd->end_point.col) { rd->position.col = rd->start_point.col; if (rd->position.row >= rd->end_point.row) { roll_display(); } else { ++rd->position.row; } } cursor_update(); }