rt-thread/components/drivers/serial/device/virtual/render.c

662 lines
15 KiB
C

/*
* 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 <rtthread.h>
#include <rtdevice.h>
#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();
}